From abc70f9e128264cc858d12190faf776225ff5d0a Mon Sep 17 00:00:00 2001 From: Bachir Soussi Chiadmi Date: Thu, 25 Oct 2018 16:18:42 +0200 Subject: [PATCH] restored dashboard from usable --- .dependencies | 26 + .editorconfig | 18 + .gitignore | 45 + .travis.yml | 101 + CHANGELOG.md | 1139 +++++++ CONTRIBUTING.md | 122 + LICENSE.txt | 21 + README.md | 5 + assets/.gitkeep | 0 backup/.gitkeep | 0 bin/composer.phar | Bin 0 -> 1181186 bytes bin/gpm | 57 + bin/grav | 46 + bin/plugin | 118 + cache/.gitkeep | 0 codeception.yml | 16 + composer.json | 54 + composer.lock | 2672 +++++++++++++++++ images/.gitkeep | 0 index.php | 43 + logs/.gitkeep | 0 robots.txt | 11 + system/assets/debugger.css | 54 + system/assets/grav.png | Bin 0 -> 548 bytes system/assets/jquery/jquery-2.1.4.min.js | 4 + system/assets/jquery/jquery-2.x.min.js | 4 + system/assets/responsive-overlays/1x.png | Bin 0 -> 3238 bytes system/assets/responsive-overlays/2x.png | Bin 0 -> 7593 bytes system/assets/responsive-overlays/3x.png | Bin 0 -> 13002 bytes system/assets/responsive-overlays/4x.png | Bin 0 -> 15545 bytes system/assets/responsive-overlays/unknown.png | Bin 0 -> 5241 bytes system/assets/whoops.css | 19 + system/blueprints/config/media.yaml | 5 + system/blueprints/config/site.yaml | 116 + system/blueprints/config/streams.yaml | 7 + system/blueprints/config/system.yaml | 900 ++++++ system/blueprints/media/meta.yaml | 7 + system/blueprints/media/move.yaml | 8 + system/blueprints/media/rename.yaml | 8 + system/blueprints/pages/default.yaml | 281 ++ system/blueprints/pages/modular.yaml | 47 + system/blueprints/pages/modular_new.yaml | 56 + system/blueprints/pages/modular_raw.yaml | 97 + system/blueprints/pages/move.yaml | 17 + system/blueprints/pages/new.yaml | 66 + system/blueprints/pages/new_folder.yaml | 35 + system/blueprints/pages/raw.yaml | 96 + system/blueprints/user/account.yaml | 85 + system/blueprints/user/account_new.yaml | 16 + system/blueprints/user/group.yaml | 44 + system/blueprints/user/group_new.yaml | 16 + system/config/media.yaml | 194 ++ system/config/site.yaml | 34 + system/config/streams.yaml | 16 + system/config/system.yaml | 118 + system/defines.php | 43 + system/images/media/thumb-doc.png | Bin 0 -> 1352 bytes system/images/media/thumb-gif.png | Bin 0 -> 1794 bytes system/images/media/thumb-gz.png | Bin 0 -> 1157 bytes system/images/media/thumb-html.png | Bin 0 -> 1063 bytes system/images/media/thumb-jpeg.png | Bin 0 -> 2002 bytes system/images/media/thumb-jpg.png | Bin 0 -> 1924 bytes system/images/media/thumb-m4v.png | Bin 0 -> 1429 bytes system/images/media/thumb-mov.png | Bin 0 -> 1524 bytes system/images/media/thumb-mp4.png | Bin 0 -> 1235 bytes system/images/media/thumb-pdf.png | Bin 0 -> 1100 bytes system/images/media/thumb-png.png | Bin 0 -> 2008 bytes system/images/media/thumb-swf.png | Bin 0 -> 1480 bytes system/images/media/thumb-txt.png | Bin 0 -> 1070 bytes system/images/media/thumb-zip.png | Bin 0 -> 1100 bytes system/images/media/thumb.png | Bin 0 -> 123 bytes system/languages/cs.yaml | 80 + system/languages/de.yaml | 83 + system/languages/el.yaml | 22 + system/languages/en.yaml | 100 + system/languages/es.yaml | 76 + system/languages/fi.yaml | 1 + system/languages/fr.yaml | 138 + system/languages/hr.yaml | 75 + system/languages/hu.yaml | 65 + system/languages/it.yaml | 46 + system/languages/lt.yaml | 69 + system/languages/nb.yaml | 2 + system/languages/nl.yaml | 51 + system/languages/no.yaml | 91 + system/languages/pl.yaml | 75 + system/languages/pt.yaml | 56 + system/languages/ro.yaml | 21 + system/languages/ru.yaml | 67 + system/languages/sk.yaml | 1 + system/languages/sv.yaml | 3 + system/languages/tr.yaml | 36 + system/src/Grav/Common/Assets.php | 1189 ++++++++ system/src/Grav/Common/Backup/ZipBackup.php | 144 + system/src/Grav/Common/Browser.php | 135 + system/src/Grav/Common/Cache.php | 345 +++ system/src/Grav/Common/Composer.php | 60 + .../src/Grav/Common/Config/CompiledBase.php | 238 ++ .../Grav/Common/Config/CompiledBlueprints.php | 49 + .../src/Grav/Common/Config/CompiledConfig.php | 98 + .../Grav/Common/Config/CompiledLanguages.php | 64 + system/src/Grav/Common/Config/Config.php | 97 + .../Grav/Common/Config/ConfigFileFinder.php | 258 ++ system/src/Grav/Common/Config/Languages.php | 44 + system/src/Grav/Common/Config/Setup.php | 254 ++ system/src/Grav/Common/Data/Blueprint.php | 476 +++ system/src/Grav/Common/Data/Blueprints.php | 145 + system/src/Grav/Common/Data/Data.php | 282 ++ system/src/Grav/Common/Data/DataInterface.php | 68 + system/src/Grav/Common/Data/Validation.php | 713 +++++ system/src/Grav/Common/Debugger.php | 217 ++ system/src/Grav/Common/Errors/Errors.php | 60 + .../Grav/Common/Errors/Resources/error.css | 52 + .../Common/Errors/Resources/layout.html.php | 30 + .../Grav/Common/Errors/SimplePageHandler.php | 96 + system/src/Grav/Common/File/CompiledFile.php | 78 + .../Grav/Common/File/CompiledMarkdownFile.php | 9 + .../src/Grav/Common/File/CompiledYamlFile.php | 9 + system/src/Grav/Common/Filesystem/Folder.php | 431 +++ .../RecursiveFolderFilterIterator.php | 44 + .../Grav/Common/GPM/AbstractCollection.php | 32 + .../GPM/Common/AbstractPackageCollection.php | 34 + .../Common/GPM/Common/CachedCollection.php | 21 + system/src/Grav/Common/GPM/Common/Package.php | 42 + system/src/Grav/Common/GPM/GPM.php | 409 +++ system/src/Grav/Common/GPM/Installer.php | 370 +++ .../GPM/Local/AbstractPackageCollection.php | 16 + system/src/Grav/Common/GPM/Local/Package.php | 32 + system/src/Grav/Common/GPM/Local/Packages.php | 17 + system/src/Grav/Common/GPM/Local/Plugins.php | 22 + system/src/Grav/Common/GPM/Local/Themes.php | 22 + .../GPM/Remote/AbstractPackageCollection.php | 54 + system/src/Grav/Common/GPM/Remote/Grav.php | 95 + system/src/Grav/Common/GPM/Remote/Package.php | 12 + .../src/Grav/Common/GPM/Remote/Packages.php | 17 + system/src/Grav/Common/GPM/Remote/Plugins.php | 26 + system/src/Grav/Common/GPM/Remote/Themes.php | 26 + system/src/Grav/Common/GPM/Response.php | 329 ++ system/src/Grav/Common/GPM/Upgrader.php | 122 + system/src/Grav/Common/Getters.php | 160 + system/src/Grav/Common/Grav.php | 570 ++++ system/src/Grav/Common/GravTrait.php | 35 + system/src/Grav/Common/Helpers/Truncator.php | 194 ++ system/src/Grav/Common/Inflector.php | 338 +++ system/src/Grav/Common/Iterator.php | 256 ++ system/src/Grav/Common/Language/Language.php | 475 +++ .../Grav/Common/Language/LanguageCodes.php | 774 +++++ system/src/Grav/Common/Markdown/Parsedown.php | 23 + .../Grav/Common/Markdown/ParsedownExtra.php | 23 + .../Common/Markdown/ParsedownGravTrait.php | 360 +++ system/src/Grav/Common/Page/Collection.php | 549 ++++ system/src/Grav/Common/Page/Header.php | 15 + system/src/Grav/Common/Page/Media.php | 258 ++ .../Grav/Common/Page/Medium/AudioMedium.php | 38 + .../src/Grav/Common/Page/Medium/ImageFile.php | 96 + .../Grav/Common/Page/Medium/ImageMedium.php | 555 ++++ system/src/Grav/Common/Page/Medium/Link.php | 65 + system/src/Grav/Common/Page/Medium/Medium.php | 494 +++ .../Grav/Common/Page/Medium/MediumFactory.php | 148 + .../Common/Page/Medium/ParsedownHtmlTrait.php | 31 + .../Page/Medium/RenderableInterface.php | 34 + .../Common/Page/Medium/StaticImageMedium.php | 28 + .../Common/Page/Medium/StaticResizeTrait.php | 20 + .../Page/Medium/ThumbnailImageMedium.php | 121 + .../Grav/Common/Page/Medium/VideoMedium.php | 38 + system/src/Grav/Common/Page/Page.php | 2448 +++++++++++++++ system/src/Grav/Common/Page/Pages.php | 1059 +++++++ system/src/Grav/Common/Page/Types.php | 113 + system/src/Grav/Common/Plugin.php | 212 ++ system/src/Grav/Common/Plugins.php | 159 + .../Common/Service/ConfigServiceProvider.php | 152 + .../Common/Service/ErrorServiceProvider.php | 18 + .../Common/Service/LoggerServiceProvider.php | 20 + .../Common/Service/StreamsServiceProvider.php | 40 + system/src/Grav/Common/Session.php | 63 + system/src/Grav/Common/Taxonomy.php | 125 + system/src/Grav/Common/Theme.php | 51 + system/src/Grav/Common/Themes.php | 327 ++ system/src/Grav/Common/Twig/Twig.php | 368 +++ .../src/Grav/Common/Twig/TwigEnvironment.php | 14 + system/src/Grav/Common/Twig/TwigExtension.php | 736 +++++ .../Grav/Common/Twig/WriteCacheFileTrait.php | 44 + system/src/Grav/Common/Uri.php | 1035 +++++++ .../src/Grav/Common/User/Authentication.php | 53 + system/src/Grav/Common/User/Group.php | 132 + system/src/Grav/Common/User/User.php | 198 ++ system/src/Grav/Common/Utils.php | 619 ++++ system/src/Grav/Console/Cli/BackupCommand.php | 83 + system/src/Grav/Console/Cli/CleanCommand.php | 237 ++ .../Grav/Console/Cli/ClearCacheCommand.php | 64 + .../src/Grav/Console/Cli/ComposerCommand.php | 70 + .../src/Grav/Console/Cli/InstallCommand.php | 162 + .../Grav/Console/Cli/NewProjectCommand.php | 62 + .../src/Grav/Console/Cli/NewUserCommand.php | 34 + .../src/Grav/Console/Cli/SandboxCommand.php | 304 ++ system/src/Grav/Console/ConsoleCommand.php | 38 + system/src/Grav/Console/ConsoleTrait.php | 127 + system/src/Grav/Console/Gpm/IndexCommand.php | 243 ++ system/src/Grav/Console/Gpm/InfoCommand.php | 176 ++ .../src/Grav/Console/Gpm/InstallCommand.php | 522 ++++ .../Grav/Console/Gpm/SelfupgradeCommand.php | 237 ++ .../src/Grav/Console/Gpm/UninstallCommand.php | 195 ++ system/src/Grav/Console/Gpm/UpdateCommand.php | 214 ++ .../src/Grav/Console/Gpm/VersionCommand.php | 92 + tests/_bootstrap.php | 55 + tests/_support/AcceptanceTester.php | 26 + tests/_support/FunctionalTester.php | 26 + tests/_support/Helper/Acceptance.php | 10 + tests/_support/Helper/Functional.php | 10 + tests/_support/Helper/Unit.php | 80 + tests/_support/UnitTester.php | 26 + tests/acceptance.suite.yml | 12 + tests/acceptance/_bootstrap.php | 2 + .../01.item1-1/01.item1-1-1/default.md | 10 + .../01.item1-1/02.item1-1-2/default.md | 10 + .../01.item1-1/03.item1-1-3/default.md | 10 + .../user/pages/01.item1/01.item1-1/default.md | 10 + .../02.item1-2/01.item1-2-1/default.md | 10 + .../02.item1-2/02.item1-2-2/default.md | 10 + .../02.item1-2/03.item1-2-3/default.md | 10 + .../user/pages/01.item1/02.item1-2/default.md | 10 + .../03.item1-3/01.item1-3-1/default.md | 10 + .../03.item1-3/02.item1-3-2/default.md | 10 + .../03.item1-3/03.item1-3-3/default.md | 10 + .../user/pages/01.item1/03.item1-3/default.md | 10 + .../user/pages/01.item1/default.md | 10 + .../user/pages/01.item1/home-cache-image.jpg | Bin 0 -> 156699 bytes .../user/pages/01.item1/home-sample-image.jpg | Bin 0 -> 156699 bytes .../01.item2-1/01.item2-1-1/default.md | 10 + .../01.item2-1/02.item2-1-2/default.md | 10 + .../01.item2-1/03.item2-1-3/default.md | 10 + .../user/pages/02.item2/01.item2-1/default.md | 10 + .../02.item2-2/01.item2-2-1/default.md | 10 + .../02.item2-2/02.item2-2-2/default.md | 10 + .../02.item2-2/03.item2-2-3/default.md | 10 + .../pages/02.item2/02.item2-2/cache-image.jpg | Bin 0 -> 156699 bytes .../user/pages/02.item2/02.item2-2/default.md | 10 + .../02.item2/02.item2-2/sample-image.jpg | Bin 0 -> 156699 bytes .../03.item2-3/01.item2-3-1/default.md | 10 + .../03.item2-3/02.item2-3-2/default.md | 10 + .../03.item2-3/03.item2-3-3/default.md | 10 + .../user/pages/02.item2/03.item2-3/default.md | 10 + .../user/pages/02.item2/default.md | 10 + .../01.item3-1/01.item3-1-1/default.md | 14 + .../01.item3-1/02.item3-1-2/default.md | 10 + .../01.item3-1/03.item3-1-3/default.md | 10 + .../user/pages/03.item3/01.item3-1/default.md | 10 + .../02.item3-2/01.item3-2-1/default.md | 10 + .../02.item3-2/02.item3-2-2/default.md | 10 + .../02.item3-2/03.item3-2-3/default.md | 10 + .../user/pages/03.item3/02.item3-2/default.md | 10 + .../03.item3-3/01.item3-3-1/default.md | 10 + .../03.item3-3/02.item3-3-2/default.md | 10 + .../03.item3-3/03.item3-3-3/default.md | 10 + .../user/pages/03.item3/03.item3-3/default.md | 10 + .../user/pages/03.item3/default.md | 10 + .../simple-site/user/pages/01.home/default.md | 10 + .../simple-site/user/pages/02.blog/blog.md | 10 + .../user/pages/02.blog/post-one/item.md | 10 + .../user/pages/02.blog/post-two/item.md | 10 + .../user/pages/03.about/default.md | 10 + .../single-pages/01.simple-page/default.md | 5 + tests/functional.suite.yml | 11 + tests/functional/_bootstrap.php | 2 + tests/unit.suite.yml | 9 + tests/unit/Grav/Common/AssetsTest.php | 432 +++ tests/unit/Grav/Common/BrowserTest.php | 47 + tests/unit/Grav/Common/ComposerTest.php | 33 + .../Grav/Common/Markdown/ParsedownTest.php | 715 +++++ tests/unit/Grav/Common/Page/PagesTest.php | 286 ++ tests/unit/Grav/Common/UriTest.php | 414 +++ tests/unit/Grav/Common/UtilsTest.php | 263 ++ tests/unit/_bootstrap.php | 4 + user/accounts/.gitkeep | 0 user/config/media.yaml | 194 ++ user/config/plugins/featherlight.yaml | 8 + user/config/site.yaml | 7 + user/config/system.yaml | 41 + user/data/.gitkeep | 0 user/pages/01.home/home.md | 9 + user/pages/02.projets | 1 + user/plugins/.gitkeep | 0 user/plugins/external_links/.gitignore | 1 + user/plugins/external_links/CHANGELOG.md | 122 + user/plugins/external_links/LICENSE | 727 +++++ user/plugins/external_links/README.md | 199 ++ .../assets/css/external_links.css | 39 + .../external_links/assets/images/link.png | Bin 0 -> 196 bytes .../external_links/assets/images/mail.png | Bin 0 -> 187 bytes user/plugins/external_links/assets/logo.png | Bin 0 -> 21194 bytes .../external_links/assets/screenshot.png | Bin 0 -> 23677 bytes user/plugins/external_links/blueprints.yaml | 173 ++ .../blueprints/external_links.yaml | 78 + .../external_links/classes/ExternalLinks.php | 485 +++ .../external_links/docs/CONTRIBUTING.md | 125 + user/plugins/external_links/docs/INSTALL.md | 47 + .../plugins/external_links/external_links.php | 237 ++ .../external_links/external_links.yaml | 42 + user/plugins/external_links/hebe.json | 15 + user/plugins/external_links/languages.yaml | 150 + user/plugins/featherlight/CHANGELOG.md | 32 + user/plugins/featherlight/LICENSE | 21 + user/plugins/featherlight/README.md | 107 + .../featherlight/assets/featherlight.png | Bin 0 -> 570321 bytes user/plugins/featherlight/blueprints.yaml | 93 + .../css/featherlight.gallery.min.css | 8 + .../featherlight/css/featherlight.min.css | 8 + user/plugins/featherlight/featherlight.php | 105 + user/plugins/featherlight/featherlight.yaml | 8 + user/plugins/featherlight/hebe.json | 15 + .../js/featherlight.gallery.min.js | 8 + .../featherlight/js/featherlight.min.js | 8 + user/plugins/taxonomylist/CHANGELOG.md | 47 + user/plugins/taxonomylist/LICENSE | 21 + user/plugins/taxonomylist/README.md | 44 + user/plugins/taxonomylist/assets/readme_1.png | Bin 0 -> 12888 bytes user/plugins/taxonomylist/blueprints.yaml | 34 + .../taxonomylist/classes/taxonomylist.php | 54 + user/plugins/taxonomylist/hebe.json | 15 + user/plugins/taxonomylist/taxonomylist.php | 58 + user/plugins/taxonomylist/taxonomylist.yaml | 2 + .../templates/partials/taxonomylist.html.twig | 10 + user/themes/.gitkeep | 0 user/themes/usabledashboard/.gitignore | 2 + user/themes/usabledashboard/CHANGELOG.md | 234 ++ user/themes/usabledashboard/LICENSE | 21 + user/themes/usabledashboard/blueprints.yaml | 35 + .../blueprints/asset/file.yaml | 70 + .../usabledashboard/blueprints/blog.yaml | 89 + .../usabledashboard/blueprints/form.yaml | 2 + .../usabledashboard/blueprints/item.yaml | 103 + .../blueprints/modular/features.yaml | 40 + .../blueprints/modular/showcase.yaml | 46 + .../blueprints/modular/text.yaml | 34 + user/themes/usabledashboard/bower.json | 20 + user/themes/usabledashboard/composer.json | 28 + user/themes/usabledashboard/css/dashboard.css | 469 +++ user/themes/usabledashboard/css/fonts.css | 25 + user/themes/usabledashboard/css/normalize.css | 424 +++ .../fonts/icon/foundation-icons-custom.eot | Bin 0 -> 30044 bytes .../fonts/icon/foundation-icons-custom.svg | 305 ++ .../fonts/icon/foundation-icons-custom.ttf | Bin 0 -> 59868 bytes .../fonts/icon/foundation-icons-custom.woff | Bin 0 -> 34672 bytes .../fonts/icon/foundation-icons.css | 645 ++++ .../fonts/icon/foundation-icons.eot | Bin 0 -> 54568 bytes .../fonts/icon/foundation-icons.svg | 970 ++++++ .../fonts/icon/foundation-icons.ttf | Bin 0 -> 56976 bytes .../fonts/icon/foundation-icons.woff | Bin 0 -> 32020 bytes .../fonts/interval/interval-webfont.ttf | Bin 0 -> 44516 bytes .../fonts/interval/interval-webfont.woff | Bin 0 -> 24944 bytes .../fonts/interval/interval_bold-webfont.ttf | Bin 0 -> 42560 bytes .../fonts/interval/interval_bold-webfont.woff | Bin 0 -> 24264 bytes .../fonts/ubuntu/Ubuntu Font License 1.0.txt | 35 + .../fonts/ubuntu/Ubuntu-B-webfont.eot | Bin 0 -> 16468 bytes .../fonts/ubuntu/Ubuntu-B-webfont.svg | 190 ++ .../fonts/ubuntu/Ubuntu-B-webfont.ttf | Bin 0 -> 31284 bytes .../fonts/ubuntu/Ubuntu-B-webfont.woff | Bin 0 -> 19060 bytes .../fonts/ubuntu/Ubuntu-BI-webfont.eot | Bin 0 -> 18356 bytes .../fonts/ubuntu/Ubuntu-BI-webfont.svg | 190 ++ .../fonts/ubuntu/Ubuntu-BI-webfont.ttf | Bin 0 -> 36444 bytes .../fonts/ubuntu/Ubuntu-BI-webfont.woff | Bin 0 -> 21040 bytes .../fonts/ubuntu/Ubuntu-C-webfont.eot | Bin 0 -> 17744 bytes .../fonts/ubuntu/Ubuntu-C-webfont.svg | 190 ++ .../fonts/ubuntu/Ubuntu-C-webfont.ttf | Bin 0 -> 34160 bytes .../fonts/ubuntu/Ubuntu-C-webfont.woff | Bin 0 -> 20356 bytes .../fonts/ubuntu/Ubuntu-L-webfont.eot | Bin 0 -> 17053 bytes .../fonts/ubuntu/Ubuntu-L-webfont.svg | 190 ++ .../fonts/ubuntu/Ubuntu-L-webfont.ttf | Bin 0 -> 32684 bytes .../fonts/ubuntu/Ubuntu-L-webfont.woff | Bin 0 -> 19564 bytes .../fonts/ubuntu/Ubuntu-LI-webfont.eot | Bin 0 -> 19336 bytes .../fonts/ubuntu/Ubuntu-LI-webfont.svg | 190 ++ .../fonts/ubuntu/Ubuntu-LI-webfont.ttf | Bin 0 -> 37964 bytes .../fonts/ubuntu/Ubuntu-LI-webfont.woff | Bin 0 -> 21984 bytes .../fonts/ubuntu/Ubuntu-M-webfont.eot | Bin 0 -> 16982 bytes .../fonts/ubuntu/Ubuntu-M-webfont.svg | 190 ++ .../fonts/ubuntu/Ubuntu-M-webfont.ttf | Bin 0 -> 31440 bytes .../fonts/ubuntu/Ubuntu-M-webfont.woff | Bin 0 -> 19596 bytes .../fonts/ubuntu/Ubuntu-MI-webfont.eot | Bin 0 -> 18832 bytes .../fonts/ubuntu/Ubuntu-MI-webfont.svg | 190 ++ .../fonts/ubuntu/Ubuntu-MI-webfont.ttf | Bin 0 -> 36492 bytes .../fonts/ubuntu/Ubuntu-MI-webfont.woff | Bin 0 -> 21404 bytes .../fonts/ubuntu/Ubuntu-R-webfont.eot | Bin 0 -> 16771 bytes .../fonts/ubuntu/Ubuntu-R-webfont.svg | 190 ++ .../fonts/ubuntu/Ubuntu-R-webfont.ttf | Bin 0 -> 31848 bytes .../fonts/ubuntu/Ubuntu-R-webfont.woff | Bin 0 -> 19312 bytes .../fonts/ubuntu/Ubuntu-RI-webfont.eot | Bin 0 -> 19184 bytes .../fonts/ubuntu/Ubuntu-RI-webfont.svg | 190 ++ .../fonts/ubuntu/Ubuntu-RI-webfont.ttf | Bin 0 -> 38596 bytes .../fonts/ubuntu/Ubuntu-RI-webfont.woff | Bin 0 -> 21848 bytes .../usabledashboard/fonts/ubuntu/demo.html | 73 + .../usabledashboard/fonts/ubuntu/ubuntu.css | 108 + user/themes/usabledashboard/gulpfile.js | 37 + user/themes/usabledashboard/hebe.json | 15 + .../themes/usabledashboard/images/favicon.png | Bin 0 -> 1177 bytes user/themes/usabledashboard/images/logo.png | Bin 0 -> 8714 bytes user/themes/usabledashboard/js/dashboard.js | 11 + .../usabledashboard/js/dist/dashboard.min.js | 2 + .../js/dist/masonry.pkgd.min.js | 9 + user/themes/usabledashboard/languages.yaml | 2 + user/themes/usabledashboard/package.json | 20 + .../scss/Skeleton-Sass-master/.editorconfig | 14 + .../scss/Skeleton-Sass-master/.gitignore | 4 + .../scss/Skeleton-Sass-master/Gruntfile.js | 32 + .../scss/Skeleton-Sass-master/README.md | 84 + .../scss/Skeleton-Sass-master/bower.json | 26 + .../Skeleton-Sass-master/images/favicon.png | Bin 0 -> 1156 bytes .../scss/Skeleton-Sass-master/index.html | 46 + .../scss/Skeleton-Sass-master/package.json | 29 + .../scss/base/_base-styles.scss | 37 + .../scss/base/_functions.scss | 17 + .../scss/base/_normalize.scss | 428 +++ .../scss/base/_typography.scss | 29 + .../scss/base/_utils.scss | 20 + .../scss/base/_variables.scss | 32 + .../scss/modules/_buttons.scss | 147 + .../scss/modules/_code.scss | 18 + .../scss/modules/_forms.scss | 81 + .../scss/modules/_grid.scss | 116 + .../scss/modules/_lists.scss | 32 + .../scss/modules/_media-queries.scss | 22 + .../scss/modules/_spacing.scss | 26 + .../scss/modules/_tables.scss | 19 + .../Skeleton-Sass-master/scss/skeleton.scss | 27 + .../usabledashboard/scss/dashboard.scss | 244 ++ .../usabledashboard/templates/blog.html.twig | 38 + .../templates/default.html.twig | 5 + .../usabledashboard/templates/error.html.twig | 12 + .../usabledashboard/templates/form.html.twig | 8 + .../templates/formdata.html.twig | 12 + .../templates/forms/data.html.twig | 1 + .../templates/forms/data.txt.twig | 1 + .../templates/forms/form.html.twig | 1 + .../usabledashboard/templates/home.html.twig | 30 + .../usabledashboard/templates/item.html.twig | 18 + .../templates/macros/macros.html.twig | 3 + .../templates/modular.html.twig | 56 + .../templates/modular/features.html.twig | 22 + .../templates/modular/showcase.html.twig | 13 + .../templates/modular/text.html.twig | 7 + .../templates/partials/base.html.twig | 66 + .../templates/partials/blog_item.html.twig | 78 + .../partials/dashboard-filters.html.twig | 18 + .../templates/partials/metadata.html.twig | 3 + .../templates/partials/navigation.html.twig | 44 + .../templates/partials/sidebar.html.twig | 42 + .../templates/project.html.twig | 74 + .../templates/snipcart.html.twig | 20 + .../usabledashboard/usabledashboard.php | 9 + .../usabledashboard/usabledashboard.yaml | 11 + webserver-configs/Caddyfile | 8 + webserver-configs/htaccess.txt | 65 + webserver-configs/lighttpd.conf | 48 + webserver-configs/nginx.conf | 44 + webserver-configs/web.config | 42 + 454 files changed, 46195 insertions(+) create mode 100644 .dependencies create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 assets/.gitkeep create mode 100644 backup/.gitkeep create mode 100755 bin/composer.phar create mode 100755 bin/gpm create mode 100755 bin/grav create mode 100755 bin/plugin create mode 100644 cache/.gitkeep create mode 100644 codeception.yml create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 images/.gitkeep create mode 100644 index.php create mode 100644 logs/.gitkeep create mode 100644 robots.txt create mode 100644 system/assets/debugger.css create mode 100644 system/assets/grav.png create mode 100644 system/assets/jquery/jquery-2.1.4.min.js create mode 100644 system/assets/jquery/jquery-2.x.min.js create mode 100644 system/assets/responsive-overlays/1x.png create mode 100644 system/assets/responsive-overlays/2x.png create mode 100644 system/assets/responsive-overlays/3x.png create mode 100644 system/assets/responsive-overlays/4x.png create mode 100644 system/assets/responsive-overlays/unknown.png create mode 100644 system/assets/whoops.css create mode 100644 system/blueprints/config/media.yaml create mode 100644 system/blueprints/config/site.yaml create mode 100644 system/blueprints/config/streams.yaml create mode 100644 system/blueprints/config/system.yaml create mode 100644 system/blueprints/media/meta.yaml create mode 100644 system/blueprints/media/move.yaml create mode 100644 system/blueprints/media/rename.yaml create mode 100644 system/blueprints/pages/default.yaml create mode 100644 system/blueprints/pages/modular.yaml create mode 100644 system/blueprints/pages/modular_new.yaml create mode 100644 system/blueprints/pages/modular_raw.yaml create mode 100644 system/blueprints/pages/move.yaml create mode 100644 system/blueprints/pages/new.yaml create mode 100644 system/blueprints/pages/new_folder.yaml create mode 100644 system/blueprints/pages/raw.yaml create mode 100644 system/blueprints/user/account.yaml create mode 100644 system/blueprints/user/account_new.yaml create mode 100644 system/blueprints/user/group.yaml create mode 100644 system/blueprints/user/group_new.yaml create mode 100644 system/config/media.yaml create mode 100644 system/config/site.yaml create mode 100644 system/config/streams.yaml create mode 100644 system/config/system.yaml create mode 100644 system/defines.php create mode 100644 system/images/media/thumb-doc.png create mode 100644 system/images/media/thumb-gif.png create mode 100644 system/images/media/thumb-gz.png create mode 100644 system/images/media/thumb-html.png create mode 100644 system/images/media/thumb-jpeg.png create mode 100644 system/images/media/thumb-jpg.png create mode 100644 system/images/media/thumb-m4v.png create mode 100644 system/images/media/thumb-mov.png create mode 100644 system/images/media/thumb-mp4.png create mode 100644 system/images/media/thumb-pdf.png create mode 100644 system/images/media/thumb-png.png create mode 100644 system/images/media/thumb-swf.png create mode 100644 system/images/media/thumb-txt.png create mode 100644 system/images/media/thumb-zip.png create mode 100644 system/images/media/thumb.png create mode 100644 system/languages/cs.yaml create mode 100644 system/languages/de.yaml create mode 100644 system/languages/el.yaml create mode 100644 system/languages/en.yaml create mode 100644 system/languages/es.yaml create mode 100644 system/languages/fi.yaml create mode 100644 system/languages/fr.yaml create mode 100644 system/languages/hr.yaml create mode 100644 system/languages/hu.yaml create mode 100644 system/languages/it.yaml create mode 100644 system/languages/lt.yaml create mode 100644 system/languages/nb.yaml create mode 100644 system/languages/nl.yaml create mode 100644 system/languages/no.yaml create mode 100644 system/languages/pl.yaml create mode 100644 system/languages/pt.yaml create mode 100644 system/languages/ro.yaml create mode 100644 system/languages/ru.yaml create mode 100644 system/languages/sk.yaml create mode 100644 system/languages/sv.yaml create mode 100644 system/languages/tr.yaml create mode 100644 system/src/Grav/Common/Assets.php create mode 100644 system/src/Grav/Common/Backup/ZipBackup.php create mode 100644 system/src/Grav/Common/Browser.php create mode 100644 system/src/Grav/Common/Cache.php create mode 100644 system/src/Grav/Common/Composer.php create mode 100644 system/src/Grav/Common/Config/CompiledBase.php create mode 100644 system/src/Grav/Common/Config/CompiledBlueprints.php create mode 100644 system/src/Grav/Common/Config/CompiledConfig.php create mode 100644 system/src/Grav/Common/Config/CompiledLanguages.php create mode 100644 system/src/Grav/Common/Config/Config.php create mode 100644 system/src/Grav/Common/Config/ConfigFileFinder.php create mode 100644 system/src/Grav/Common/Config/Languages.php create mode 100644 system/src/Grav/Common/Config/Setup.php create mode 100644 system/src/Grav/Common/Data/Blueprint.php create mode 100644 system/src/Grav/Common/Data/Blueprints.php create mode 100644 system/src/Grav/Common/Data/Data.php create mode 100644 system/src/Grav/Common/Data/DataInterface.php create mode 100644 system/src/Grav/Common/Data/Validation.php create mode 100644 system/src/Grav/Common/Debugger.php create mode 100644 system/src/Grav/Common/Errors/Errors.php create mode 100644 system/src/Grav/Common/Errors/Resources/error.css create mode 100644 system/src/Grav/Common/Errors/Resources/layout.html.php create mode 100644 system/src/Grav/Common/Errors/SimplePageHandler.php create mode 100644 system/src/Grav/Common/File/CompiledFile.php create mode 100644 system/src/Grav/Common/File/CompiledMarkdownFile.php create mode 100644 system/src/Grav/Common/File/CompiledYamlFile.php create mode 100644 system/src/Grav/Common/Filesystem/Folder.php create mode 100644 system/src/Grav/Common/Filesystem/RecursiveFolderFilterIterator.php create mode 100644 system/src/Grav/Common/GPM/AbstractCollection.php create mode 100644 system/src/Grav/Common/GPM/Common/AbstractPackageCollection.php create mode 100644 system/src/Grav/Common/GPM/Common/CachedCollection.php create mode 100644 system/src/Grav/Common/GPM/Common/Package.php create mode 100644 system/src/Grav/Common/GPM/GPM.php create mode 100644 system/src/Grav/Common/GPM/Installer.php create mode 100644 system/src/Grav/Common/GPM/Local/AbstractPackageCollection.php create mode 100644 system/src/Grav/Common/GPM/Local/Package.php create mode 100644 system/src/Grav/Common/GPM/Local/Packages.php create mode 100644 system/src/Grav/Common/GPM/Local/Plugins.php create mode 100644 system/src/Grav/Common/GPM/Local/Themes.php create mode 100644 system/src/Grav/Common/GPM/Remote/AbstractPackageCollection.php create mode 100644 system/src/Grav/Common/GPM/Remote/Grav.php create mode 100644 system/src/Grav/Common/GPM/Remote/Package.php create mode 100644 system/src/Grav/Common/GPM/Remote/Packages.php create mode 100644 system/src/Grav/Common/GPM/Remote/Plugins.php create mode 100644 system/src/Grav/Common/GPM/Remote/Themes.php create mode 100644 system/src/Grav/Common/GPM/Response.php create mode 100644 system/src/Grav/Common/GPM/Upgrader.php create mode 100644 system/src/Grav/Common/Getters.php create mode 100644 system/src/Grav/Common/Grav.php create mode 100644 system/src/Grav/Common/GravTrait.php create mode 100644 system/src/Grav/Common/Helpers/Truncator.php create mode 100644 system/src/Grav/Common/Inflector.php create mode 100644 system/src/Grav/Common/Iterator.php create mode 100644 system/src/Grav/Common/Language/Language.php create mode 100644 system/src/Grav/Common/Language/LanguageCodes.php create mode 100644 system/src/Grav/Common/Markdown/Parsedown.php create mode 100644 system/src/Grav/Common/Markdown/ParsedownExtra.php create mode 100644 system/src/Grav/Common/Markdown/ParsedownGravTrait.php create mode 100644 system/src/Grav/Common/Page/Collection.php create mode 100644 system/src/Grav/Common/Page/Header.php create mode 100644 system/src/Grav/Common/Page/Media.php create mode 100644 system/src/Grav/Common/Page/Medium/AudioMedium.php create mode 100644 system/src/Grav/Common/Page/Medium/ImageFile.php create mode 100644 system/src/Grav/Common/Page/Medium/ImageMedium.php create mode 100644 system/src/Grav/Common/Page/Medium/Link.php create mode 100644 system/src/Grav/Common/Page/Medium/Medium.php create mode 100644 system/src/Grav/Common/Page/Medium/MediumFactory.php create mode 100644 system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php create mode 100644 system/src/Grav/Common/Page/Medium/RenderableInterface.php create mode 100644 system/src/Grav/Common/Page/Medium/StaticImageMedium.php create mode 100644 system/src/Grav/Common/Page/Medium/StaticResizeTrait.php create mode 100644 system/src/Grav/Common/Page/Medium/ThumbnailImageMedium.php create mode 100644 system/src/Grav/Common/Page/Medium/VideoMedium.php create mode 100644 system/src/Grav/Common/Page/Page.php create mode 100644 system/src/Grav/Common/Page/Pages.php create mode 100644 system/src/Grav/Common/Page/Types.php create mode 100644 system/src/Grav/Common/Plugin.php create mode 100644 system/src/Grav/Common/Plugins.php create mode 100644 system/src/Grav/Common/Service/ConfigServiceProvider.php create mode 100644 system/src/Grav/Common/Service/ErrorServiceProvider.php create mode 100644 system/src/Grav/Common/Service/LoggerServiceProvider.php create mode 100644 system/src/Grav/Common/Service/StreamsServiceProvider.php create mode 100644 system/src/Grav/Common/Session.php create mode 100644 system/src/Grav/Common/Taxonomy.php create mode 100644 system/src/Grav/Common/Theme.php create mode 100644 system/src/Grav/Common/Themes.php create mode 100644 system/src/Grav/Common/Twig/Twig.php create mode 100644 system/src/Grav/Common/Twig/TwigEnvironment.php create mode 100644 system/src/Grav/Common/Twig/TwigExtension.php create mode 100644 system/src/Grav/Common/Twig/WriteCacheFileTrait.php create mode 100644 system/src/Grav/Common/Uri.php create mode 100644 system/src/Grav/Common/User/Authentication.php create mode 100644 system/src/Grav/Common/User/Group.php create mode 100644 system/src/Grav/Common/User/User.php create mode 100644 system/src/Grav/Common/Utils.php create mode 100644 system/src/Grav/Console/Cli/BackupCommand.php create mode 100644 system/src/Grav/Console/Cli/CleanCommand.php create mode 100644 system/src/Grav/Console/Cli/ClearCacheCommand.php create mode 100644 system/src/Grav/Console/Cli/ComposerCommand.php create mode 100644 system/src/Grav/Console/Cli/InstallCommand.php create mode 100644 system/src/Grav/Console/Cli/NewProjectCommand.php create mode 100644 system/src/Grav/Console/Cli/NewUserCommand.php create mode 100644 system/src/Grav/Console/Cli/SandboxCommand.php create mode 100644 system/src/Grav/Console/ConsoleCommand.php create mode 100644 system/src/Grav/Console/ConsoleTrait.php create mode 100644 system/src/Grav/Console/Gpm/IndexCommand.php create mode 100644 system/src/Grav/Console/Gpm/InfoCommand.php create mode 100644 system/src/Grav/Console/Gpm/InstallCommand.php create mode 100644 system/src/Grav/Console/Gpm/SelfupgradeCommand.php create mode 100644 system/src/Grav/Console/Gpm/UninstallCommand.php create mode 100644 system/src/Grav/Console/Gpm/UpdateCommand.php create mode 100644 system/src/Grav/Console/Gpm/VersionCommand.php create mode 100644 tests/_bootstrap.php create mode 100644 tests/_support/AcceptanceTester.php create mode 100644 tests/_support/FunctionalTester.php create mode 100644 tests/_support/Helper/Acceptance.php create mode 100644 tests/_support/Helper/Functional.php create mode 100644 tests/_support/Helper/Unit.php create mode 100644 tests/_support/UnitTester.php create mode 100644 tests/acceptance.suite.yml create mode 100644 tests/acceptance/_bootstrap.php create mode 100644 tests/fake/nested-site/user/pages/01.item1/01.item1-1/01.item1-1-1/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/01.item1-1/02.item1-1-2/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/01.item1-1/03.item1-1-3/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/01.item1-1/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/02.item1-2/01.item1-2-1/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/02.item1-2/02.item1-2-2/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/02.item1-2/03.item1-2-3/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/02.item1-2/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/03.item1-3/01.item1-3-1/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/03.item1-3/02.item1-3-2/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/03.item1-3/03.item1-3-3/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/03.item1-3/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/home-cache-image.jpg create mode 100644 tests/fake/nested-site/user/pages/01.item1/home-sample-image.jpg create mode 100644 tests/fake/nested-site/user/pages/02.item2/01.item2-1/01.item2-1-1/default.md create mode 100644 tests/fake/nested-site/user/pages/02.item2/01.item2-1/02.item2-1-2/default.md create mode 100644 tests/fake/nested-site/user/pages/02.item2/01.item2-1/03.item2-1-3/default.md create mode 100644 tests/fake/nested-site/user/pages/02.item2/01.item2-1/default.md create mode 100644 tests/fake/nested-site/user/pages/02.item2/02.item2-2/01.item2-2-1/default.md create mode 100644 tests/fake/nested-site/user/pages/02.item2/02.item2-2/02.item2-2-2/default.md create mode 100644 tests/fake/nested-site/user/pages/02.item2/02.item2-2/03.item2-2-3/default.md create mode 100644 tests/fake/nested-site/user/pages/02.item2/02.item2-2/cache-image.jpg create mode 100644 tests/fake/nested-site/user/pages/02.item2/02.item2-2/default.md create mode 100644 tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg create mode 100644 tests/fake/nested-site/user/pages/02.item2/03.item2-3/01.item2-3-1/default.md create mode 100644 tests/fake/nested-site/user/pages/02.item2/03.item2-3/02.item2-3-2/default.md create mode 100644 tests/fake/nested-site/user/pages/02.item2/03.item2-3/03.item2-3-3/default.md create mode 100644 tests/fake/nested-site/user/pages/02.item2/03.item2-3/default.md create mode 100644 tests/fake/nested-site/user/pages/02.item2/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/01.item3-1/01.item3-1-1/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/01.item3-1/02.item3-1-2/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/01.item3-1/03.item3-1-3/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/01.item3-1/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/02.item3-2/01.item3-2-1/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/02.item3-2/02.item3-2-2/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/02.item3-2/03.item3-2-3/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/02.item3-2/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/03.item3-3/01.item3-3-1/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/03.item3-3/02.item3-3-2/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/03.item3-3/03.item3-3-3/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/03.item3-3/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/default.md create mode 100644 tests/fake/simple-site/user/pages/01.home/default.md create mode 100644 tests/fake/simple-site/user/pages/02.blog/blog.md create mode 100644 tests/fake/simple-site/user/pages/02.blog/post-one/item.md create mode 100644 tests/fake/simple-site/user/pages/02.blog/post-two/item.md create mode 100644 tests/fake/simple-site/user/pages/03.about/default.md create mode 100644 tests/fake/single-pages/01.simple-page/default.md create mode 100644 tests/functional.suite.yml create mode 100644 tests/functional/_bootstrap.php create mode 100644 tests/unit.suite.yml create mode 100644 tests/unit/Grav/Common/AssetsTest.php create mode 100644 tests/unit/Grav/Common/BrowserTest.php create mode 100644 tests/unit/Grav/Common/ComposerTest.php create mode 100644 tests/unit/Grav/Common/Markdown/ParsedownTest.php create mode 100644 tests/unit/Grav/Common/Page/PagesTest.php create mode 100644 tests/unit/Grav/Common/UriTest.php create mode 100644 tests/unit/Grav/Common/UtilsTest.php create mode 100644 tests/unit/_bootstrap.php create mode 100644 user/accounts/.gitkeep create mode 100644 user/config/media.yaml create mode 100644 user/config/plugins/featherlight.yaml create mode 100644 user/config/site.yaml create mode 100644 user/config/system.yaml create mode 100644 user/data/.gitkeep create mode 100644 user/pages/01.home/home.md create mode 100755 user/pages/02.projets create mode 100644 user/plugins/.gitkeep create mode 100644 user/plugins/external_links/.gitignore create mode 100644 user/plugins/external_links/CHANGELOG.md create mode 100644 user/plugins/external_links/LICENSE create mode 100644 user/plugins/external_links/README.md create mode 100644 user/plugins/external_links/assets/css/external_links.css create mode 100644 user/plugins/external_links/assets/images/link.png create mode 100644 user/plugins/external_links/assets/images/mail.png create mode 100644 user/plugins/external_links/assets/logo.png create mode 100644 user/plugins/external_links/assets/screenshot.png create mode 100644 user/plugins/external_links/blueprints.yaml create mode 100644 user/plugins/external_links/blueprints/external_links.yaml create mode 100644 user/plugins/external_links/classes/ExternalLinks.php create mode 100644 user/plugins/external_links/docs/CONTRIBUTING.md create mode 100644 user/plugins/external_links/docs/INSTALL.md create mode 100644 user/plugins/external_links/external_links.php create mode 100644 user/plugins/external_links/external_links.yaml create mode 100644 user/plugins/external_links/hebe.json create mode 100644 user/plugins/external_links/languages.yaml create mode 100644 user/plugins/featherlight/CHANGELOG.md create mode 100644 user/plugins/featherlight/LICENSE create mode 100644 user/plugins/featherlight/README.md create mode 100644 user/plugins/featherlight/assets/featherlight.png create mode 100644 user/plugins/featherlight/blueprints.yaml create mode 100644 user/plugins/featherlight/css/featherlight.gallery.min.css create mode 100644 user/plugins/featherlight/css/featherlight.min.css create mode 100644 user/plugins/featherlight/featherlight.php create mode 100644 user/plugins/featherlight/featherlight.yaml create mode 100644 user/plugins/featherlight/hebe.json create mode 100644 user/plugins/featherlight/js/featherlight.gallery.min.js create mode 100644 user/plugins/featherlight/js/featherlight.min.js create mode 100644 user/plugins/taxonomylist/CHANGELOG.md create mode 100644 user/plugins/taxonomylist/LICENSE create mode 100644 user/plugins/taxonomylist/README.md create mode 100644 user/plugins/taxonomylist/assets/readme_1.png create mode 100644 user/plugins/taxonomylist/blueprints.yaml create mode 100644 user/plugins/taxonomylist/classes/taxonomylist.php create mode 100644 user/plugins/taxonomylist/hebe.json create mode 100644 user/plugins/taxonomylist/taxonomylist.php create mode 100644 user/plugins/taxonomylist/taxonomylist.yaml create mode 100644 user/plugins/taxonomylist/templates/partials/taxonomylist.html.twig create mode 100644 user/themes/.gitkeep create mode 100644 user/themes/usabledashboard/.gitignore create mode 100644 user/themes/usabledashboard/CHANGELOG.md create mode 100644 user/themes/usabledashboard/LICENSE create mode 100644 user/themes/usabledashboard/blueprints.yaml create mode 100644 user/themes/usabledashboard/blueprints/asset/file.yaml create mode 100644 user/themes/usabledashboard/blueprints/blog.yaml create mode 100644 user/themes/usabledashboard/blueprints/form.yaml create mode 100644 user/themes/usabledashboard/blueprints/item.yaml create mode 100644 user/themes/usabledashboard/blueprints/modular/features.yaml create mode 100644 user/themes/usabledashboard/blueprints/modular/showcase.yaml create mode 100644 user/themes/usabledashboard/blueprints/modular/text.yaml create mode 100644 user/themes/usabledashboard/bower.json create mode 100644 user/themes/usabledashboard/composer.json create mode 100644 user/themes/usabledashboard/css/dashboard.css create mode 100644 user/themes/usabledashboard/css/fonts.css create mode 100644 user/themes/usabledashboard/css/normalize.css create mode 100644 user/themes/usabledashboard/fonts/icon/foundation-icons-custom.eot create mode 100644 user/themes/usabledashboard/fonts/icon/foundation-icons-custom.svg create mode 100644 user/themes/usabledashboard/fonts/icon/foundation-icons-custom.ttf create mode 100644 user/themes/usabledashboard/fonts/icon/foundation-icons-custom.woff create mode 100644 user/themes/usabledashboard/fonts/icon/foundation-icons.css create mode 100644 user/themes/usabledashboard/fonts/icon/foundation-icons.eot create mode 100644 user/themes/usabledashboard/fonts/icon/foundation-icons.svg create mode 100644 user/themes/usabledashboard/fonts/icon/foundation-icons.ttf create mode 100644 user/themes/usabledashboard/fonts/icon/foundation-icons.woff create mode 100644 user/themes/usabledashboard/fonts/interval/interval-webfont.ttf create mode 100644 user/themes/usabledashboard/fonts/interval/interval-webfont.woff create mode 100644 user/themes/usabledashboard/fonts/interval/interval_bold-webfont.ttf create mode 100644 user/themes/usabledashboard/fonts/interval/interval_bold-webfont.woff create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu Font License 1.0.txt create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-B-webfont.eot create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-B-webfont.svg create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-B-webfont.ttf create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-B-webfont.woff create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-BI-webfont.eot create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-BI-webfont.svg create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-BI-webfont.ttf create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-BI-webfont.woff create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-C-webfont.eot create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-C-webfont.svg create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-C-webfont.ttf create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-C-webfont.woff create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-L-webfont.eot create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-L-webfont.svg create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-L-webfont.ttf create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-L-webfont.woff create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-LI-webfont.eot create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-LI-webfont.svg create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-LI-webfont.ttf create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-LI-webfont.woff create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-M-webfont.eot create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-M-webfont.svg create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-M-webfont.ttf create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-M-webfont.woff create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-MI-webfont.eot create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-MI-webfont.svg create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-MI-webfont.ttf create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-MI-webfont.woff create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-R-webfont.eot create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-R-webfont.svg create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-R-webfont.ttf create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-R-webfont.woff create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-RI-webfont.eot create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-RI-webfont.svg create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-RI-webfont.ttf create mode 100644 user/themes/usabledashboard/fonts/ubuntu/Ubuntu-RI-webfont.woff create mode 100644 user/themes/usabledashboard/fonts/ubuntu/demo.html create mode 100644 user/themes/usabledashboard/fonts/ubuntu/ubuntu.css create mode 100644 user/themes/usabledashboard/gulpfile.js create mode 100644 user/themes/usabledashboard/hebe.json create mode 100644 user/themes/usabledashboard/images/favicon.png create mode 100644 user/themes/usabledashboard/images/logo.png create mode 100644 user/themes/usabledashboard/js/dashboard.js create mode 100644 user/themes/usabledashboard/js/dist/dashboard.min.js create mode 100644 user/themes/usabledashboard/js/dist/masonry.pkgd.min.js create mode 100644 user/themes/usabledashboard/languages.yaml create mode 100644 user/themes/usabledashboard/package.json create mode 100644 user/themes/usabledashboard/scss/Skeleton-Sass-master/.editorconfig create mode 100644 user/themes/usabledashboard/scss/Skeleton-Sass-master/.gitignore create mode 100644 user/themes/usabledashboard/scss/Skeleton-Sass-master/Gruntfile.js create mode 100644 user/themes/usabledashboard/scss/Skeleton-Sass-master/README.md create mode 100644 user/themes/usabledashboard/scss/Skeleton-Sass-master/bower.json create mode 100644 user/themes/usabledashboard/scss/Skeleton-Sass-master/images/favicon.png create mode 100644 user/themes/usabledashboard/scss/Skeleton-Sass-master/index.html create mode 100644 user/themes/usabledashboard/scss/Skeleton-Sass-master/package.json create mode 100644 user/themes/usabledashboard/scss/Skeleton-Sass-master/scss/base/_base-styles.scss create mode 100644 user/themes/usabledashboard/scss/Skeleton-Sass-master/scss/base/_functions.scss create mode 100644 user/themes/usabledashboard/scss/Skeleton-Sass-master/scss/base/_normalize.scss create mode 100644 user/themes/usabledashboard/scss/Skeleton-Sass-master/scss/base/_typography.scss create mode 100644 user/themes/usabledashboard/scss/Skeleton-Sass-master/scss/base/_utils.scss create mode 100644 user/themes/usabledashboard/scss/Skeleton-Sass-master/scss/base/_variables.scss create mode 100644 user/themes/usabledashboard/scss/Skeleton-Sass-master/scss/modules/_buttons.scss create mode 100644 user/themes/usabledashboard/scss/Skeleton-Sass-master/scss/modules/_code.scss create mode 100644 user/themes/usabledashboard/scss/Skeleton-Sass-master/scss/modules/_forms.scss create mode 100644 user/themes/usabledashboard/scss/Skeleton-Sass-master/scss/modules/_grid.scss create mode 100644 user/themes/usabledashboard/scss/Skeleton-Sass-master/scss/modules/_lists.scss create mode 100644 user/themes/usabledashboard/scss/Skeleton-Sass-master/scss/modules/_media-queries.scss create mode 100644 user/themes/usabledashboard/scss/Skeleton-Sass-master/scss/modules/_spacing.scss create mode 100644 user/themes/usabledashboard/scss/Skeleton-Sass-master/scss/modules/_tables.scss create mode 100644 user/themes/usabledashboard/scss/Skeleton-Sass-master/scss/skeleton.scss create mode 100644 user/themes/usabledashboard/scss/dashboard.scss create mode 100644 user/themes/usabledashboard/templates/blog.html.twig create mode 100644 user/themes/usabledashboard/templates/default.html.twig create mode 100644 user/themes/usabledashboard/templates/error.html.twig create mode 100644 user/themes/usabledashboard/templates/form.html.twig create mode 100644 user/themes/usabledashboard/templates/formdata.html.twig create mode 100644 user/themes/usabledashboard/templates/forms/data.html.twig create mode 100644 user/themes/usabledashboard/templates/forms/data.txt.twig create mode 100644 user/themes/usabledashboard/templates/forms/form.html.twig create mode 100644 user/themes/usabledashboard/templates/home.html.twig create mode 100644 user/themes/usabledashboard/templates/item.html.twig create mode 100644 user/themes/usabledashboard/templates/macros/macros.html.twig create mode 100644 user/themes/usabledashboard/templates/modular.html.twig create mode 100644 user/themes/usabledashboard/templates/modular/features.html.twig create mode 100644 user/themes/usabledashboard/templates/modular/showcase.html.twig create mode 100644 user/themes/usabledashboard/templates/modular/text.html.twig create mode 100644 user/themes/usabledashboard/templates/partials/base.html.twig create mode 100644 user/themes/usabledashboard/templates/partials/blog_item.html.twig create mode 100644 user/themes/usabledashboard/templates/partials/dashboard-filters.html.twig create mode 100644 user/themes/usabledashboard/templates/partials/metadata.html.twig create mode 100644 user/themes/usabledashboard/templates/partials/navigation.html.twig create mode 100644 user/themes/usabledashboard/templates/partials/sidebar.html.twig create mode 100644 user/themes/usabledashboard/templates/project.html.twig create mode 100644 user/themes/usabledashboard/templates/snipcart.html.twig create mode 100644 user/themes/usabledashboard/usabledashboard.php create mode 100644 user/themes/usabledashboard/usabledashboard.yaml create mode 100644 webserver-configs/Caddyfile create mode 100644 webserver-configs/htaccess.txt create mode 100644 webserver-configs/lighttpd.conf create mode 100644 webserver-configs/nginx.conf create mode 100644 webserver-configs/web.config diff --git a/.dependencies b/.dependencies new file mode 100644 index 0000000..3e82e06 --- /dev/null +++ b/.dependencies @@ -0,0 +1,26 @@ +git: + problems: + url: https://github.com/getgrav/grav-plugin-problems + path: user/plugins/problems + branch: master + error: + url: https://github.com/getgrav/grav-plugin-error + path: user/plugins/error + branch: master + antimatter: + url: https://github.com/getgrav/grav-theme-antimatter + path: user/themes/antimatter + branch: master +links: + problems: + src: grav-plugin-problems + path: user/plugins/problems + scm: github + error: + src: grav-plugin-error + path: user/plugins/error + scm: github + antimatter: + src: grav-theme-antimatter + path: user/themes/antimatter + scm: github diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..685c063 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 + +# 2 space indentation +[*.yaml, *.yml] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f247a9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +# Composer +.composer +vendor/ + +# Sass +.sass-cache + +# Grav Specific +backup/* +!backup/.* +cache/* +!cache/.* +#assets/* +#!assets/.* +logs/* +!logs/.* +images/* +!images/.* +user/accounts/* +!user/accounts/.* +#user/data/* +#!user/data/.* +#user/plugins/* +#!user/plugins/.* +#user/themes/* +#!user/themes/.* +user/localhost/config/security.yaml + +# OS Generated +.DS_Store* +ehthumbs.db +Icon? +Thumbs.db +*.swp + +# phpstorm +.idea/* +user/config/security.yaml + +tests/_output/* +tests/_support/_generated/* +tests/cache/* +tests/error.log + +.htaccess diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..fbcc77d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,101 @@ +language: php +php: + - '5.5' + - '5.6' + - '7.0' + - hhvm +branches: + only: + - develop + - master + - build_test +notifications: + email: + on_success: never + on_failure: always + hipchat: + # hipchat_api@grav + rooms: + - secure: "bqO0wM1B7bJnQw2fuhquSXEqI9gw6WmFytIh9sEWXzbYTzTUP5t0PcKOd3FT2BNMRaDxPJLVl+vG/oqmqDUBkEmOGcG504IQjeNzZqnMz0tXQMIcCc22Las9tFfc4Jf6RVi/qGomFtHGE9Wgii+TAN4zqZaufbNjwd8SyjO0+W8=" + template: + - '%{repository}#%{build_number} (%{branch}): Travis Job Finished [%{duration}] (Details)' + format: html + slack: + secure: dowksPsxxCxGKT6nis5hUgkp6+ZDAhoqzQHF9rJnx4hx0iEygPhVBs7pKl9yL2jubYJoLs+EXwE7z1dYgDAEJh4BnfrCokCMLpFGcxVxQC/HeAUdSQ2/RtdBYR5PRT75ScaFpqM/SfXXZVtnwVXAw9Z+JC6BjQ9vmn23m51Jw4k= +env: + global: + # Colors! + - TEXTRESET=$(tput sgr0) # reset the foreground colour + - RED=$(tput setaf 1) + - GREEN=$(tput setaf 2) + - YELLOW=$(tput setaf 3) + - BLUE=$(tput setaf 4) + - BOLD=$(tput bold) + # User + - GH_USER="getgrav" + # Paths + - RT_DEVTOOLS=$HOME/devtools + - GOPATH="$HOME/go" + - PATH="$GOPATH/bin:$PATH" + # GH_TOKEN [API Key] + - secure: "jS+c+g2v33vypG4VtqiSDW2qQ4dGJZlrUKBRCztoy1yrOrYRPvc5Vzi/AS3fDmZ4yizukEwmUNNzyZQcgFvLPpmCCml46Dovp8R9OXhbNe8OnULmaSn2Zkr71oblMYu6ZP+RpYvNq0BIdSB3u2TiFriHMiTIkX9UwZNaUCOX1ig=" + # BB_TOKEN value => "user:pass@" + - secure: "einUtSEkUWy2IrqLXyVjwUU+mwaaoiOXRRVdLBpA3Zye6bZx8cm5h/5AplkPWhM/NmCJoW/MwNZHHkFhlr3mDRov5iOxVmTTYfnXB+I5lxYTSgduOLLErS7mU8hfADpVDU8bHNU44fNGD3UEiG1PD4qQBX4DMlqIFmR20mjs81k=" + # GH_API_USER [for curl] + - secure: "Xbk/V9aIys0NxccJGR3Zrm2GRxDnA0RuazBs1puIboTYDhbi0Z7JTL+mOx3xp5Kfoniad/xAuijQESTM9MMrKqq/qCzhAMaC1+vcL4pCHZH4NSG6DBxB9BPkKVFq+1llu5FTEf8bkxHzwGR0l1ARW6TVRcgTHr5B58bCEIwEOrI=" + +before_install: + - export TZ=Pacific/Honolulu + - composer self-update + - if [ $TRAVIS_BRANCH == 'develop' ] || [ $TRAVIS_PULL_REQUEST != 'false' ]; then + composer install --dev --prefer-dist; + fi + - if [ $TRAVIS_BRANCH != 'develop' ] && [ $TRAVIS_PHP_VERSION == '5.6' ] && [ $TRAVIS_PULL_REQUEST == "false" ]; then + 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); + go get github.com/aktau/github-release; + git clone --quiet --depth=50 --branch=master https://${BB_TOKEN}bitbucket.org/rockettheme/grav-devtools.git $RT_DEVTOOLS &>/dev/null; + if [ ! -z "$TRAVIS_TAG" ]; then + cd ${RT_DEVTOOLS}; + ./build-grav.sh skeletons.txt; + fi; + fi +before_script: + - if [ $TRAVIS_PHP_VERSION != 'hhvm' ]; then phpenv config-rm xdebug.ini; fi +script: + - if [ $TRAVIS_BRANCH == 'develop' ] || [ $TRAVIS_PULL_REQUEST != 'false' ]; then + vendor/bin/codecept run; + else + 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); + fi + - if [ ! -z "$TRAVIS_TAG" ] && [ $TRAVIS_BRANCH != 'develop' ] && [ $TRAVIS_PHP_VERSION == '5.6' ] && [ $TRAVIS_PULL_REQUEST == "false" ]; then + FILES="$RT_DEVTOOLS/grav-dist/*.zip"; + for file in ${FILES[@]}; do + NAME=${file##*/}; + if [[ "$NAME" == *"-rc"* ]]; then + REPO="$(echo ${NAME} | rev | cut -f 3- -d "-" | rev)"; + else + REPO="$(echo ${NAME} | rev | cut -f 2- -d "-" | rev)"; + fi; + if [[ $REPO == 'grav' || $REPO == 'grav-admin' || $REPO == 'grav-update' ]]; then + REPO="grav"; + fi; + API="$(curl --fail --user "${GH_API_USER}" -s https://api.github.com/repos/${GH_USER}/${REPO}/releases/latest)"; + ASSETS="$(echo "${API}" | node gh-assets.js)"; + TAG="$(echo "${API}" | grep tag_name | head -n 1 | cut -d '"' -f 4)"; + if [ $REPO == "grav" ]; then + TAG="$TRAVIS_TAG"; + fi; + if [ ! -z "$ASSETS" ]; then + for asset in ${ASSETS[@]}; do + asset_id=$(echo ${asset} | cut -d ':' -f 1); + asset_name=$(echo ${asset} | cut -d ':' -f 2); + if [ "${NAME}" == "${asset_name}" ]; then + echo -e "\nAsset ${BOLD}${BLUE}${NAME}${TEXTRESET} already exists in ${YELLOW}${REPO}${TEXTRESET}@${BOLD}${YELLOW}${TAG}${TEXTRESET}... deleting id ${BOLD}${RED}${asset_id}${TEXTRESET}..."; + curl -X DELETE --fail --user "${GH_API_USER}" "https://api.github.com/repos/${GH_USER}/${REPO}/releases/assets/${asset_id}"; + fi; + done; + fi; + echo "Uploading package ${BOLD}${BLUE}${NAME}${TEXTRESET} to ${YELLOW}${REPO}${TEXTRESET}@${YELLOW}${TAG}${TEXTRESET}"; + github-release upload --security-token $GH_TOKEN --user ${GH_USER} --repo $REPO --tag "$TAG" --name "$NAME" --file "$file"; + done; + fi diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..93cee99 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,1139 @@ +# v1.0.9 +## 02/05/2016 + +1. [](#new) + * New **Unit Testing** via Codeception http://codeception.com/ + * New **page-level SSL** functionality when using `absolute_urls` + * Added `reverse_proxy` config option for issues with non-standard ports + * Added `proxy_url` config option to support GPM behind proxy servers #639 + * New `Pages::parentsRawRoutes()` method + * Enhanced `bin/gpm info` CLI command with Changelog support #559 + * Ability to add empty *Folder* via admin plugin + * Added latest `jQuery 2.2.0` library to core + * Added translations from Crowdin +1. [](#improved) + * [BC] Metadata now supports only flat arrays. To use open graph metas and the likes (ie, 'og:title'), simply specify it in the key. + * Refactored `Uri::convertUrl()` method to be more reliable + tests created + * Date for last update of a modular sub-page sets modified date of modular page itself + * Split configuration up into two steps + * Moved Grav-based `base_uri` variables into `Uri::init()` + * Refactored init in `URI` to better support testing + * Allow `twig_vars` to be exposed earlier and merged later + * Avoid setting empty metadata + * Accept single group access as a string rather than requiring an array + * Return `$this` in Page constructor and init to allow chaining + * Added `ext-*` PHP requirements to `composer.json` + * Use Whoops 2.0 library while supporting old style + * Removed redundant old default-hash fallback mechanisms + * Commented out default redirects and routes in `site.yaml` + * Added `/tests` folder to deny's of all `webserver-configs/*` files + * Various PS and code style fixes +1. [](#bugfix) + * Fix default generator metadata + * Fix for broken image processing caused by `Uri::convertUrl()` bugs + * Fix loading JS and CSS from collections #623 + * Fix stream overriding + * Remove the URL extension for home link + * Fix permissions when the user has no access level set at all + * Fix issue with user with multiple groups getting denied on first group + * Fixed an issue with `Pages()` internal cache lookup not being unique enough + * Fix for bug with `site.redirects` and `site.routes` being an empty list + * [Markdown] Don't process links for **special protocols** + * [Whoops] serve JSON errors when request is JSON + + +# v1.0.8 +## 01/08/2016 + +1. [](#new) + * Added `rotate`, `flip` and `fixOrientation` image medium methods +1. [](#bugfix) + * Removed IP from Nonce generation. Should be more reliable in a variety of scenarios + +# v1.0.7 +## 01/07/2016 + +1. [](#new) + * Added `composer create-project` as an additional installation method #585 + * New optional system config setting to strip home from page routs and urls #561 + * Added Greek, Finnish, Norwegian, Polish, Portuguese, and Romanian languages + * Added new `Page->topParent()` method to return top most parent of a page + * Added plugins configuration tab to debugger + * Added support for APCu and PHP7.0 via new Doctrine Cache release + * Added global setting for `twig_first` processing (false by default) + * New configuration options for Session settings #553 +1. [](#improved) + * Switched to SSL for GPM calls + * Use `URI->host()` for session domain + * Add support for `open_basedir` when installing packages via GPM + * Improved `Utils::generateNonceString()` method to handle reverse proxies + * Optimized core thumbnails saving 38% in file size + * Added new `bin/gpm index --installed-only` option + * Improved GPM errors to provider more helpful diagnostic of issues + * Removed old hardcoded PHP version references + * Moved `onPageContentProcessed()` event so it's fired more reliably + * Maintain md5 keys during sorting of Assets #566 + * Update to Caddyfile for Caddy web server +1. [](#bugfix) + * Fixed an issue with cache/config checksum not being set on cache load + * Fix for page blueprint and theme inheritance issue #534 + * Set `ZipBackup` timeout to 10 minutes if possible + * Fix case where we only have inline data for CSS or JS #565 + * Fix `bin/grav sandbox` command to work with new `webserver-config` folder + * Fix for markdown attributes on external URLs + * Fixed issue where `data:` page header was acting as `publish_date:` + * Fix for special characters in URL parameters (e.g. /tag:c++) #541 + * Safety check for an array of nonces to only use the first one + +# v1.0.6 +## 12/22/2015 + +1. [](#new) + * Set minimum requirements to [PHP 5.5.9](http://bit.ly/1Jt9OXO) + * Added `saveConfig` to Themes +1. [](#improved) + * Updated Whoops to new 2.0 version (PHP 7.0 compatible) + * Moved sample web server configs into dedicated directory + * FastCGI will use Apache's `mod_deflate` if gzip turned off +1. [](#bugfix) + * Fix broken media image operators + * Only call extra method of blueprints if blueprints exist + * Fix lang prefix in url twig variables #523 + * Fix case insensitive HTTPS check #535 + * Field field validation handles case `multiple` missing + +# v1.0.5 +## 12/18/2015 + +1. [](#new) + * Add ability to extend markdown with plugins + * Added support for plugins to have individual language files + * Added `7z` to media formats + * Use Grav's fork of Parsedown until PR is merged + * New function to persist plugin configuration to disk + * GPM `selfupgrade` will now check PHP version requirements +1. [](#improved) + * If the field allows multiple files, return array + * Handle non-array values in file validation +1. [](#bugfix) + * Fix when looping `fields` param in a `list` field + * Properly convert commas to spaces for media attributes + * Forcing Travis VM to HI timezone to address future files in zip file + +# v1.0.4 +## 12/12/2015 + +1. [](#bugfix) + * Needed to put default image folder permissions for YAML compatibility + +# v1.0.3 +## 12/11/2015 + +1. [](#bugfix) + * Fixed issue when saving config causing incorrect image cache folder perms + +# v1.0.2 +## 12/11/2015 + +1. [](#bugfix) + * Fix for timing display in debugbar + +# v1.0.1 +## 12/11/2015 + +1. [](#improved) + * Reduced package sizes by removing extra vendor dev bits +1. [](#bugfix) + * Fix issue when you enable debugger from admin plugin + +# 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 + +1. [](#new) + * Refactor Config classes for improved performance! + * Refactor Data classes to use `NestedArrayAccess` instead of `DataMutatorTrait` + * Added support for `classes` and `id` on medium objects to set CSS values + * Data objects: Allow function call chaining + * Data objects: Lazy load blueprints only if needed + * Automatically create unique security salt for each configuration + * Added Hungarian translation + * Added support for User groups +1. [](#improved) + * Improved robots.txt to disallow crawling of non-user folders + * Nonces only generated once per action and process + * Added IP into Nonce string calculation + * Nonces now use random string with random salt to improve performance + * Improved list form handling #475 + * Vendor library updates +1. [](#bugfix) + * Fixed help output for `bin/plugin` + * Fix for nested logic for lists and form parsing #273 + * Fix for array form fields and last entry not getting deleted + * Should not be able to set parent to self #308 + +# v1.0.0-rc.5 +## 11/20/2015 + +1. [](#new) + * Added **nonce** functionality for all admin forms for improved security + * Implemented the ability for Plugins to provide their own CLI commands through `bin/plugin` + * Added Croatian translation + * Added missing `umask_fix` property to `system.yaml` + * Added current theme's config to global config. E.g. `config.theme.dropdown_enabled` + * Added `append_url_extension` option to system config & page headers + * Users have a new `state` property to allow disabling/banning + * Added new `Page.relativePagePath()` helper method + * Added new `|pad` Twig filter for strings (uses `str_pad()`) + * Added `lighttpd.conf` for Lightly web server +1. [](#improved) + * Clear previously applied operations when doing a reset on image media + * Password no longer required when editing user + * Improved support for trailing `/` URLs + * Improved `.nginx.conf` configuration file + * Improved `.htaccess` security + * Updated vendor libs + * Updated `composer.phar` + * Use streams instead of paths for `clearCache()` + * Use PCRE_UTF8 so unicode strings can be regexed in Truncator + * Handle case when login plugin is disabled + * Improved `quality` functionality in media handling + * Added some missing translation strings + * Deprecated `bin/grav newuser` in favor of `bin/plugin login new-user` + * Moved fallback types to use any valid media type + * Renamed `system.pages.fallback_types` to `system.media.allowed_fallback_types` + * Removed version number in default `generator` meta tag + * Disable time limit in case of slow downloads + * Removed default hash in `system.yaml` +1. [](#bugfix) + * Fix for media using absolute URLs causing broken links + * Fix theme auto-loading #432 + * Don't create empty `\n"; + } + + + return $output; + } + + /** + * Build the JavaScript script tags. + * + * @param string $group name of the group + * @param array $attributes + * + * @return string + */ + public function js($group = 'head', $attributes = []) + { + if (!$this->js && !$this->inline_js) { + return null; + } + + // Sort array by priorities (larger priority first) + uasort($this->js, function ($a, $b) { + if ($a['priority'] == $b['priority']) { + return $b['order'] - $a['order']; + } + + return $a['priority'] - $b['priority']; + }); + + uasort($this->inline_js, function ($a, $b) { + if ($a['priority'] == $b['priority']) { + return $b['order'] - $a['order']; + } + + return $a['priority'] - $b['priority']; + }); + + $this->js = array_reverse($this->js); + $this->inline_js = array_reverse($this->inline_js); + + $attributes = $this->attributes(array_merge(['type' => 'text/javascript'], $attributes)); + + $output = ''; + $inline_js = ''; + + if ($this->js_pipeline) { + $pipeline_result = $this->pipelineJs($group); + if ($pipeline_result) { + $output .= '' . "\n"; + } + foreach ($this->js_no_pipeline as $file) { + if ($group && $file['group'] == $group) { + $output .= '' . "\n"; + } + } + } else { + foreach ($this->js as $file) { + if ($group && $file['group'] == $group) { + $output .= '' . "\n"; + } + } + } + + // Render Inline JS + foreach ($this->inline_js as $inline) { + if ($group && $inline['group'] == $group) { + $inline_js .= $inline['asset'] . "\n"; + } + } + + if ($inline_js) { + $output .= "\n\n"; + } + + return $output; + } + + /** + * Minify and concatenate CSS + * + * @param string $group + * + * @return bool|string + */ + protected function pipelineCss($group = 'head') + { + /** @var Cache $cache */ + $cache = self::getGrav()['cache']; + $key = '?' . $cache->getKey(); + + // temporary list of assets to pipeline + $temp_css = []; + + // clear no-pipeline assets lists + $this->css_no_pipeline = []; + + $file = md5(json_encode($this->css) . $this->css_minify . $this->css_rewrite . $group) . '.css'; + + $relative_path = "{$this->base_url}{$this->assets_url}/{$file}"; + $absolute_path = $this->assets_dir . $file; + + // If pipeline exist return it + if (file_exists($absolute_path)) { + return $relative_path . $key; + } + + // Remove any non-pipeline files + foreach ($this->css as $id => $asset) { + if ($asset['group'] == $group) { + if (!$asset['pipeline']) { + $this->css_no_pipeline[$id] = $asset; + } else { + $temp_css[$id] = $asset; + } + } + } + + //if nothing found get out of here! + if (count($temp_css) == 0) { + return false; + } + + $css_minify = $this->css_minify; + + // If this is a Windows server, and minify_windows is false (default value) skip the + // minification process because it will cause Apache to die/crash due to insufficient + // ThreadStackSize in httpd.conf - See: https://bugs.php.net/bug.php?id=47689 + if (strtoupper(substr(php_uname('s'), 0, 3)) === 'WIN' && !$this->css_minify_windows) { + $css_minify = false; + } + + // Concatenate files + $buffer = $this->gatherLinks($temp_css, CSS_ASSET); + if ($css_minify) { + $min = new \CSSmin(); + $buffer = $min->run($buffer); + } + + // Write file + if (strlen(trim($buffer)) > 0) { + file_put_contents($absolute_path, $buffer); + + return $relative_path . $key; + } else { + return false; + } + } + + /** + * Minify and concatenate JS files. + * + * @param string $group + * + * @return string + */ + protected function pipelineJs($group = 'head') + { + /** @var Cache $cache */ + $cache = self::getGrav()['cache']; + $key = '?' . $cache->getKey(); + + // temporary list of assets to pipeline + $temp_js = []; + + // clear no-pipeline assets lists + $this->js_no_pipeline = []; + + $file = md5(json_encode($this->js) . $this->js_minify . $group) . '.js'; + + $relative_path = "{$this->base_url}{$this->assets_url}/{$file}"; + $absolute_path = $this->assets_dir . $file; + + // If pipeline exist return it + if (file_exists($absolute_path)) { + return $relative_path . $key; + } + + // Remove any non-pipeline files + foreach ($this->js as $id => $asset) { + if ($asset['group'] == $group) { + if (!$asset['pipeline']) { + $this->js_no_pipeline[] = $asset; + } else { + $temp_js[$id] = $asset; + } + } + } + + //if nothing found get out of here! + if (count($temp_js) == 0) { + return false; + } + + // Concatenate files + $buffer = $this->gatherLinks($temp_js, JS_ASSET); + if ($this->js_minify) { + $buffer = \JSMin::minify($buffer); + } + + // Write file + if (strlen(trim($buffer)) > 0) { + file_put_contents($absolute_path, $buffer); + + return $relative_path . $key; + } else { + return false; + } + } + + /** + * Return the array of all the registered CSS assets + * + * @return array + */ + public function getCss() + { + return $this->css; + } + + /** + * Return the array of all the registered JS assets + * + * @return array + */ + public function getJs() + { + return $this->js; + } + + /** + * Return the array of all the registered collections + * + * @return array + */ + public function getCollections() + { + return $this->collections; + } + + /** + * Determines if an asset exists as a collection, CSS or JS reference + * + * @param $asset + * + * @return bool + */ + public function exists($asset) + { + if (isset($this->collections[$asset]) || isset($this->css[$asset]) || isset($this->js[$asset])) { + return true; + } else { + return false; + } + } + + /** + * Add/replace collection. + * + * @param string $collectionName + * @param array $assets + * @param bool $overwrite + * + * @return $this + */ + public function registerCollection($collectionName, Array $assets, $overwrite = false) + { + if ($overwrite || !isset($this->collections[$collectionName])) { + $this->collections[$collectionName] = $assets; + } + + return $this; + } + + /** + * Reset all assets. + * + * @return $this + */ + public function reset() + { + return $this->resetCss()->resetJs(); + } + + /** + * Reset JavaScript assets. + * + * @return $this + */ + public function resetJs() + { + $this->js = []; + $this->inline_js = []; + + return $this; + } + + /** + * Reset CSS assets. + * + * @return $this + */ + public function resetCss() + { + $this->css = []; + $this->inline_css = []; + + return $this; + } + + /** + * Add all JavaScript assets within $directory + * + * @param string $directory Relative to the Grav root path, or a stream identifier + * + * @return $this + */ + public function addDirJs($directory) + { + return $this->addDir($directory, self::JS_REGEX); + } + + /** + * Add all CSS assets within $directory + * + * @param string $directory Relative to the Grav root path, or a stream identifier + * + * @return $this + */ + public function addDirCss($directory) + { + return $this->addDir($directory, self::CSS_REGEX); + } + + /** + * Add all assets matching $pattern within $directory. + * + * @param string $directory Relative to the Grav root path, or a stream identifier + * @param string $pattern (regex) + * + * @return $this + * @throws Exception + */ + public function addDir($directory, $pattern = self::DEFAULT_REGEX) + { + $root_dir = rtrim(ROOT_DIR, '/'); + + // Check if $directory is a stream. + if (strpos($directory, '://')) { + $directory = self::$grav['locator']->findResource($directory, null); + } + + // Get files + $files = $this->rglob($root_dir . DIRECTORY_SEPARATOR . $directory, $pattern, $root_dir . '/'); + + // No luck? Nothing to do + if (!$files) { + return $this; + } + + // Add CSS files + if ($pattern === self::CSS_REGEX) { + foreach ($files as $file) { + $this->addCss($file); + } + + return $this; + } + + // Add JavaScript files + if ($pattern === self::JS_REGEX) { + foreach ($files as $file) { + $this->addJs($file); + } + + return $this; + } + + // Unknown pattern. + foreach ($files as $asset) { + $this->add($asset); + } + + return $this; + } + + /** + * Determine whether a link is local or remote. + * + * Understands both "http://" and "https://" as well as protocol agnostic links "//" + * + * @param string $link + * + * @return bool + */ + protected function isRemoteLink($link) + { + return ('http://' === substr($link, 0, 7) || 'https://' === substr($link, 0, 8) || '//' === substr($link, 0, + 2)); + } + + /** + * Build local links including grav asset shortcodes + * + * @param string $asset the asset string reference + * + * @return string the final link url to the asset + */ + protected function buildLocalLink($asset) + { + try { + $asset = self::getGrav()['locator']->findResource($asset, false); + } catch (\Exception $e) { + } + + return $asset ? $this->base_url . ltrim($asset, '/') : false; + } + + /** + * Build an HTML attribute string from an array. + * + * @param array $attributes + * + * @return string + */ + protected function attributes(array $attributes) + { + $html = ''; + + foreach ($attributes as $key => $value) { + // For numeric keys we will assume that the key and the value are the same + // as this will convert HTML attributes such as "required" to a correct + // form like required="required" instead of using incorrect numerics. + if (is_numeric($key)) { + $key = $value; + } + if (is_array($value)) { + $value = implode(' ', $value); + } + + $element = $key . '="' . htmlentities($value, ENT_QUOTES, 'UTF-8', false) . '"'; + $html .= ' ' . $element; + } + + return $html; + } + + /** + * Download and concatenate the content of several links. + * + * @param array $links + * @param bool $css + * + * @return string + */ + protected function gatherLinks(array $links, $css = true) + { + $buffer = ''; + $local = true; + + foreach ($links as $asset) { + $relative_dir = ''; + + $link = $asset['asset']; + $relative_path = $link; + + if ($this->isRemoteLink($link)) { + $local = false; + if ('//' === substr($link, 0, 2)) { + $link = 'http:' . $link; + } + } else { + // Fix to remove relative dir if grav is in one + if (($this->base_url != '/') && (strpos($this->base_url, $link) == 0)) { + $base_url = '#' . preg_quote($this->base_url, '#') . '#'; + $relative_path = ltrim(preg_replace($base_url, '/', $link, 1), '/'); + } + + $relative_dir = dirname($relative_path); + $link = ROOT_DIR . $relative_path; + } + + $file = ($this->fetch_command instanceof Closure) ? @$this->fetch_command->__invoke($link) : @file_get_contents($link); + + // No file found, skip it... + if ($file === false) { + continue; + } + + // Double check last character being + if (!$css) { + $file = rtrim($file, ' ;') . ';'; + } + + // If this is CSS + the file is local + rewrite enabled + if ($css && $local && $this->css_rewrite) { + $file = $this->cssRewrite($file, $relative_dir); + } + + $buffer .= $file; + } + + // Pull out @imports and move to top + if ($css) { + $buffer = $this->moveImports($buffer); + } + + return $buffer; + } + + /** + * Finds relative CSS urls() and rewrites the URL with an absolute one + * + * @param string $file the css source file + * @param string $relative_path relative path to the css file + * + * @return mixed + */ + protected function cssRewrite($file, $relative_path) + { + // Strip any sourcemap comments + $file = preg_replace(self::CSS_SOURCEMAP_REGEX, '', $file); + + // Find any css url() elements, grab the URLs and calculate an absolute path + // Then replace the old url with the new one + $file = preg_replace_callback(self::CSS_URL_REGEX, function ($matches) use ($relative_path) { + + $old_url = $matches[1]; + + // ensure this is not a data url + if (strpos($old_url, 'data:') === 0) { + return $matches[0]; + } + + $new_url = $this->base_url . ltrim(Utils::normalizePath($relative_path . '/' . $old_url), '/'); + + return str_replace($old_url, $new_url, $matches[0]); + }, $file); + + return $file; + } + + /** + * Moves @import statements to the top of the file per the CSS specification + * + * @param string $file the file containing the combined CSS files + * + * @return string the modified file with any @imports at the top of the file + */ + protected function moveImports($file) + { + $this->imports = []; + + $file = preg_replace_callback(self::CSS_IMPORT_REGEX, function ($matches) { + $this->imports[] = $matches[0]; + + return ''; + }, $file); + + return implode("\n", $this->imports) . "\n\n" . $file; + } + + /** + * Recursively get files matching $pattern within $directory. + * + * @param string $directory + * @param string $pattern (regex) + * @param string $ltrim Will be trimmed from the left of the file path + * + * @return array + */ + protected function rglob($directory, $pattern, $ltrim = null) + { + $iterator = new RegexIterator(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory, + FilesystemIterator::SKIP_DOTS)), $pattern); + $offset = strlen($ltrim); + $files = []; + + foreach ($iterator as $file) { + $files[] = substr($file->getPathname(), $offset); + } + + return $files; + } + + /** + * 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; + } + + /** + * @return string + */ + public function __toString() + { + return ''; + } + + /** + * @param $a + * @param $b + * + * @return mixed + */ + protected function priorityCompare($a, $b) + { + return $a ['priority'] - $b ['priority']; + } + +} diff --git a/system/src/Grav/Common/Backup/ZipBackup.php b/system/src/Grav/Common/Backup/ZipBackup.php new file mode 100644 index 0000000..a3dedc8 --- /dev/null +++ b/system/src/Grav/Common/Backup/ZipBackup.php @@ -0,0 +1,144 @@ +findResource('backup://', true); + + if (!$destination) + throw new \RuntimeException('The backup folder is missing.'); + + Folder::mkdir($destination); + } + + $name = self::getGrav()['config']->get('site.title', basename(GRAV_ROOT)); + + $inflector = new Inflector(); + + if (is_dir($destination)) { + $date = date('YmdHis', time()); + $filename = trim($inflector->hyphenize($name), '-') . '-' . $date . '.zip'; + $destination = rtrim($destination, DS) . DS . $filename; + } + + $messager && $messager([ + 'type' => 'message', + 'level' => 'info', + 'message' => 'Creating new Backup "' . $destination . '"' + ]); + $messager && $messager([ + 'type' => 'message', + 'level' => 'info', + 'message' => '' + ]); + + $zip = new \ZipArchive(); + $zip->open($destination, \ZipArchive::CREATE); + + $max_execution_time = ini_set('max_execution_time', 600); + + static::folderToZip(GRAV_ROOT, $zip, strlen(rtrim(GRAV_ROOT, DS) . DS), $messager); + + $messager && $messager([ + 'type' => 'progress', + 'percentage' => false, + 'complete' => true + ]); + + $messager && $messager([ + 'type' => 'message', + 'level' => 'info', + 'message' => '' + ]); + $messager && $messager([ + 'type' => 'message', + 'level' => 'info', + 'message' => 'Saving and compressing archive...' + ]); + + $zip->close(); + + if ($max_execution_time !== false) { + ini_set('max_execution_time', $max_execution_time); + } + + return $destination; + } + + /** + * @param $folder + * @param $zipFile + * @param $exclusiveLength + * @param $messager + */ + private static function folderToZip($folder, \ZipArchive &$zipFile, $exclusiveLength, callable $messager = null) + { + $handle = opendir($folder); + while (false !== $f = readdir($handle)) { + if ($f != '.' && $f != '..') { + $filePath = "$folder/$f"; + // Remove prefix from file path before add to zip. + $localPath = substr($filePath, $exclusiveLength); + + if (in_array($f, static::$ignoreFolders)) { + continue; + } elseif (in_array($localPath, static::$ignorePaths)) { + $zipFile->addEmptyDir($f); + continue; + } + + if (is_file($filePath)) { + $zipFile->addFile($filePath, $localPath); + + $messager && $messager([ + 'type' => 'progress', + 'percentage' => false, + 'complete' => false + ]); + } elseif (is_dir($filePath)) { + // Add sub-directory. + $zipFile->addEmptyDir($localPath); + static::folderToZip($filePath, $zipFile, $exclusiveLength, $messager); + } + } + } + closedir($handle); + } +} diff --git a/system/src/Grav/Common/Browser.php b/system/src/Grav/Common/Browser.php new file mode 100644 index 0000000..3272901 --- /dev/null +++ b/system/src/Grav/Common/Browser.php @@ -0,0 +1,135 @@ +useragent = parse_user_agent(); + } catch (\InvalidArgumentException $e) { + $this->useragent = parse_user_agent("Mozilla/5.0 (compatible; Unknown;)"); + } + } + + /** + * Get the current browser identifier + * + * Currently detected browsers: + * + * Android Browser + * BlackBerry Browser + * Camino + * Kindle / Silk + * Firefox / Iceweasel + * Safari + * Internet Explorer + * IEMobile + * Chrome + * Opera + * Midori + * Vivaldi + * TizenBrowser + * Lynx + * Wget + * Curl + * + * @return string the lowercase browser name + */ + public function getBrowser() + { + return strtolower($this->useragent['browser']); + } + + /** + * Get the current platform identifier + * + * Currently detected platforms: + * + * Desktop + * -> Windows + * -> Linux + * -> Macintosh + * -> Chrome OS + * Mobile + * -> Android + * -> iPhone + * -> iPad / iPod Touch + * -> Windows Phone OS + * -> Kindle + * -> Kindle Fire + * -> BlackBerry + * -> Playbook + * -> Tizen + * Console + * -> Nintendo 3DS + * -> New Nintendo 3DS + * -> Nintendo Wii + * -> Nintendo WiiU + * -> PlayStation 3 + * -> PlayStation 4 + * -> PlayStation Vita + * -> Xbox 360 + * -> Xbox One + * + * @return string the lowercase platform name + */ + public function getPlatform() + { + return strtolower($this->useragent['platform']); + } + + /** + * Get the current full version identifier + * + * @return string the browser full version identifier + */ + public function getLongVersion() + { + return $this->useragent['version']; + } + + /** + * Get the current major version identifier + * + * @return string the browser major version identifier + */ + public function getVersion() + { + $version = explode('.', $this->getLongVersion()); + + return intval($version[0]); + } + + /** + * Determine if the request comes from a human, or from a bot/crawler + * + * @return bool + */ + public function isHuman() + { + $browser = $this->getBrowser(); + if (empty($browser)) { + return false; + } + + if (preg_match('~(bot|crawl)~i', $browser)) { + return false; + } + + return true; + } +} diff --git a/system/src/Grav/Common/Cache.php b/system/src/Grav/Common/Cache.php new file mode 100644 index 0000000..e938c32 --- /dev/null +++ b/system/src/Grav/Common/Cache.php @@ -0,0 +1,345 @@ +init($grav); + } + + /** + * Initialization that sets a base key and the driver based on configuration settings + * + * @param Grav $grav + * + * @return void + */ + public function init(Grav $grav) + { + /** @var Config $config */ + $this->config = $grav['config']; + $this->now = time(); + + $this->cache_dir = $grav['locator']->findResource('cache://doctrine', true, true); + + /** @var Uri $uri */ + $uri = $grav['uri']; + + $prefix = $this->config->get('system.cache.prefix'); + + $this->enabled = (bool)$this->config->get('system.cache.enabled'); + + // Cache key allows us to invalidate all cache on configuration changes. + $this->key = ($prefix ? $prefix : 'g') . '-' . substr(md5($uri->rootUrl(true) . $this->config->key() . GRAV_VERSION), + 2, 8); + + $this->driver_setting = $this->config->get('system.cache.driver'); + + $this->driver = $this->getCacheDriver(); + + // Set the cache namespace to our unique key + $this->driver->setNamespace($this->key); + + // Dump Cache state + $grav['debugger']->addMessage('Cache: [' . ($this->enabled ? 'true' : 'false') . '] Setting: [' . $this->driver_setting . '] Driver: [' . $this->driver_name . ']'); + + } + + /** + * Automatically picks the cache mechanism to use. If you pick one manually it will use that + * If there is no config option for $driver in the config, or it's set to 'auto', it will + * pick the best option based on which cache extensions are installed. + * + * @return DoctrineCache\CacheProvider The cache driver to use + */ + public function getCacheDriver() + { + $setting = $this->driver_setting; + $driver_name = 'file'; + + if (!$setting || $setting == 'auto') { + if (extension_loaded('apcu')) { + $driver_name = 'apcu'; + } elseif (extension_loaded('apc')) { + $driver_name = 'apc'; + } elseif (extension_loaded('wincache')) { + $driver_name = 'wincache'; + } elseif (extension_loaded('xcache')) { + $driver_name = 'xcache'; + } + } else { + $driver_name = $setting; + } + + $this->driver_name = $driver_name; + + switch ($driver_name) { + case 'apc': + $driver = new DoctrineCache\ApcCache(); + break; + + case 'apcu': + $driver = new DoctrineCache\ApcuCache(); + break; + + case 'wincache': + $driver = new DoctrineCache\WinCacheCache(); + break; + + case 'xcache': + $driver = new DoctrineCache\XcacheCache(); + break; + + case 'memcache': + $memcache = new \Memcache(); + $memcache->connect($this->config->get('system.cache.memcache.server', 'localhost'), + $this->config->get('system.cache.memcache.port', 11211)); + $driver = new DoctrineCache\MemcacheCache(); + $driver->setMemcache($memcache); + break; + + case 'redis': + $redis = new \Redis(); + $redis->connect($this->config->get('system.cache.redis.server', 'localhost'), + $this->config->get('system.cache.redis.port', 6379)); + + $driver = new DoctrineCache\RedisCache(); + $driver->setRedis($redis); + break; + + default: + $driver = new DoctrineCache\FilesystemCache($this->cache_dir); + break; + } + + return $driver; + } + + /** + * Gets a cached entry if it exists based on an id. If it does not exist, it returns false + * + * @param string $id the id of the cached entry + * + * @return object returns the cached entry, can be any type, or false if doesn't exist + */ + public function fetch($id) + { + if ($this->enabled) { + return $this->driver->fetch($id); + } else { + return false; + } + } + + /** + * Stores a new cached entry. + * + * @param string $id the id of the cached entry + * @param array|object $data the data for the cached entry to store + * @param int $lifetime the lifetime to store the entry in seconds + */ + public function save($id, $data, $lifetime = null) + { + if ($this->enabled) { + if ($lifetime === null) { + $lifetime = $this->getLifetime(); + } + $this->driver->save($id, $data, $lifetime); + } + } + + /** + * Getter method to get the cache key + */ + public function getKey() + { + return $this->key; + } + + /** + * Helper method to clear all Grav caches + * + * @param string $remove standard|all|assets-only|images-only|cache-only + * + * @return array + */ + public static function clearCache($remove = 'standard') + { + $locator = self::getGrav()['locator']; + $output = []; + $user_config = USER_DIR . 'config/system.yaml'; + + switch ($remove) { + case 'all': + $remove_paths = self::$all_remove; + break; + case 'assets-only': + $remove_paths = self::$assets_remove; + break; + case 'images-only': + $remove_paths = self::$images_remove; + break; + case 'cache-only': + $remove_paths = self::$cache_remove; + break; + default: + $remove_paths = self::$standard_remove; + } + + + foreach ($remove_paths as $stream) { + + // Convert stream to a real path + $path = $locator->findResource($stream, true, true); + // Make sure path exists before proceeding, otherwise we would wipe ROOT_DIR + if (!$path) { + throw new \RuntimeException("Stream '{$stream}' not found", 500); + } + + $anything = false; + $files = glob($path . '/*'); + + if (is_array($files)) { + foreach ($files as $file) { + if (is_file($file)) { + if (@unlink($file)) { + $anything = true; + } + } elseif (is_dir($file)) { + if (@Folder::delete($file)) { + $anything = true; + } + } + } + } + + if ($anything) { + $output[] = 'Cleared: ' . $path . '/*'; + } + } + + $output[] = ''; + + if (($remove == 'all' || $remove == 'standard') && file_exists($user_config)) { + touch($user_config); + + $output[] = 'Touched: ' . $user_config; + $output[] = ''; + } + + return $output; + } + + + /** + * Set the cache lifetime programmatically + * + * @param int $future timestamp + */ + public function setLifetime($future) + { + if (!$future) { + return; + } + + $interval = $future - $this->now; + if ($interval > 0 && $interval < $this->getLifetime()) { + $this->lifetime = $interval; + } + } + + + /** + * Retrieve the cache lifetime (in seconds) + * + * @return mixed + */ + public function getLifetime() + { + if ($this->lifetime === null) { + $this->lifetime = $this->config->get('system.cache.lifetime') ?: 604800; // 1 week default + } + + return $this->lifetime; + } +} diff --git a/system/src/Grav/Common/Composer.php b/system/src/Grav/Common/Composer.php new file mode 100644 index 0000000..804bc4c --- /dev/null +++ b/system/src/Grav/Common/Composer.php @@ -0,0 +1,60 @@ +cacheFolder = $cacheFolder; + $this->files = $files; + $this->path = $path ? rtrim($path, '\\/') . '/' : ''; + } + + /** + * Get filename for the compiled PHP file. + * + * @param string $name + * @return $this + */ + public function name($name = null) + { + if (!$this->name) { + $this->name = $name ?: md5(json_encode(array_keys($this->files))); + } + + return $this; + } + + /** + * Function gets called when cached configuration is saved. + */ + public function modified() {} + + /** + * Load the configuration. + * + * @return mixed + */ + public function load() + { + if ($this->object) { + return $this->object; + } + + $filename = $this->createFilename(); + if (!$this->loadCompiledFile($filename) && $this->loadFiles()) { + $this->saveCompiledFile($filename); + } + + return $this->object; + } + + /** + * Returns checksum from the configuration files. + * + * You can set $this->checksum = false to disable this check. + * + * @return bool|string + */ + public function checksum() + { + if (!isset($this->checksum)) { + $this->checksum = md5(json_encode($this->files) . $this->version); + } + + return $this->checksum; + } + + protected function createFilename() + { + return "{$this->cacheFolder}/{$this->name()->name}.php"; + } + + /** + * Create configuration object. + * + * @param array $data + */ + abstract protected function createObject(array $data = []); + + /** + * Finalize configuration object. + */ + abstract protected function finalizeObject(); + + /** + * Load single configuration file and append it to the correct position. + * + * @param string $name Name of the position. + * @param string $filename File to be loaded. + */ + abstract protected function loadFile($name, $filename); + + /** + * Load and join all configuration files. + * + * @return bool + * @internal + */ + protected function loadFiles() + { + $this->createObject(); + + $list = array_reverse($this->files); + foreach ($list as $files) { + foreach ($files as $name => $item) { + $this->loadFile($name, $this->path . $item['file']); + } + } + + $this->finalizeObject(); + + return true; + } + + /** + * Load compiled file. + * + * @param string $filename + * @return bool + * @internal + */ + protected function loadCompiledFile($filename) + { + if (!file_exists($filename)) { + return false; + } + + $cache = include $filename; + if ( + !is_array($cache) + || !isset($cache['checksum']) + || !isset($cache['data']) + || !isset($cache['@class']) + || $cache['@class'] != get_class($this) + ) { + return false; + } + + // Load real file if cache isn't up to date (or is invalid). + if ($cache['checksum'] !== $this->checksum()) { + return false; + } + + $this->createObject($cache['data']); + + $this->finalizeObject(); + + return true; + } + + /** + * Save compiled file. + * + * @param string $filename + * @throws \RuntimeException + * @internal + */ + protected function saveCompiledFile($filename) + { + $file = PhpFile::instance($filename); + + // Attempt to lock the file for writing. + try { + $file->lock(false); + } catch (\Exception $e) { + // Another process has locked the file; we will check this in a bit. + } + + if ($file->locked() === false) { + // File was already locked by another process. + return; + } + + $cache = [ + '@class' => get_class($this), + 'timestamp' => time(), + 'checksum' => $this->checksum(), + 'files' => $this->files, + 'data' => $this->object->toArray() + ]; + + $file->save($cache); + $file->unlock(); + $file->free(); + + $this->modified(); + } +} diff --git a/system/src/Grav/Common/Config/CompiledBlueprints.php b/system/src/Grav/Common/Config/CompiledBlueprints.php new file mode 100644 index 0000000..d78a0e0 --- /dev/null +++ b/system/src/Grav/Common/Config/CompiledBlueprints.php @@ -0,0 +1,49 @@ +object = new Blueprints($data); + } + + /** + * Finalize configuration object. + */ + protected function finalizeObject() {} + + /** + * Load single configuration file and append it to the correct position. + * + * @param string $name Name of the position. + * @param string $filename File to be loaded. + */ + protected function loadFile($name, $filename) + { + $file = CompiledYamlFile::instance($filename); + $this->object->embed($name, $file->content(), '/'); + $file->free(); + } +} diff --git a/system/src/Grav/Common/Config/CompiledConfig.php b/system/src/Grav/Common/Config/CompiledConfig.php new file mode 100644 index 0000000..cb9e59d --- /dev/null +++ b/system/src/Grav/Common/Config/CompiledConfig.php @@ -0,0 +1,98 @@ +callable = $blueprints; + + return $this; + } + + /** + * @param bool $withDefaults + * @return mixed + */ + public function load($withDefaults = false) + { + $this->withDefaults = $withDefaults; + + return parent::load(); + } + + /** + * Create configuration object. + * + * @param array $data + */ + protected function createObject(array $data = []) + { + if ($this->withDefaults && empty($data) && is_callable($this->callable)) { + $blueprints = $this->callable; + $data = $blueprints()->getDefaults(); + } + + $this->object = new Config($data, $this->callable); + } + + /** + * Finalize configuration object. + */ + protected function finalizeObject() + { + $this->object->checksum($this->checksum()); + } + + /** + * Function gets called when cached configuration is saved. + */ + public function modified() + { + $this->object->modified(true); + } + + /** + * Load single configuration file and append it to the correct position. + * + * @param string $name Name of the position. + * @param string $filename File to be loaded. + */ + protected function loadFile($name, $filename) + { + $file = CompiledYamlFile::instance($filename); + $this->object->join($name, $file->content(), '/'); + $file->free(); + } +} diff --git a/system/src/Grav/Common/Config/CompiledLanguages.php b/system/src/Grav/Common/Config/CompiledLanguages.php new file mode 100644 index 0000000..ce4eb59 --- /dev/null +++ b/system/src/Grav/Common/Config/CompiledLanguages.php @@ -0,0 +1,64 @@ +object = new Languages($data); + } + + /** + * Finalize configuration object. + */ + protected function finalizeObject() + { + $this->object->checksum($this->checksum()); + } + + + /** + * Function gets called when cached configuration is saved. + */ + public function modified() + { + $this->object->modified(true); + } + + /** + * Load single configuration file and append it to the correct position. + * + * @param string $name Name of the position. + * @param string $filename File to be loaded. + */ + protected function loadFile($name, $filename) + { + $file = CompiledYamlFile::instance($filename); + if (preg_match('|languages\.yaml$|', $filename)) { + $this->object->mergeRecursive($file->content()); + } else { + $this->object->join($name, $file->content(), '/'); + } + $file->free(); + } +} diff --git a/system/src/Grav/Common/Config/Config.php b/system/src/Grav/Common/Config/Config.php new file mode 100644 index 0000000..dc51697 --- /dev/null +++ b/system/src/Grav/Common/Config/Config.php @@ -0,0 +1,97 @@ +checksum(); + } + + public function checksum($checksum = null) + { + if ($checksum !== null) { + $this->checksum = $checksum; + } + + return $this->checksum; + } + + public function modified($modified = null) + { + if ($modified !== null) { + $this->modified = $modified; + } + + return $this->modified; + } + + public function reload() + { + $grav = Grav::instance(); + + // Load new configuration. + $config = ConfigServiceProvider::load($grav); + + /** @var Debugger $debugger */ + $debugger = $grav['debugger']; + + if ($config->modified()) { + // Update current configuration. + $this->items = $config->toArray(); + $this->checksum($config->checksum()); + $this->modified(true); + + $debugger->addMessage('Configuration was changed and saved.'); + } + + return $this; + } + + public function debug() + { + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + + $debugger->addMessage('Environment Name: ' . $this->environment); + if ($this->modified()) { + $debugger->addMessage('Configuration reloaded and cached.'); + } + } + + public function init() + { + $setup = Grav::instance()['setup']->toArray(); + foreach ($setup as $key => $value) { + if ($key === 'streams' || !is_array($value)) { + // Optimized as streams and simple values are fully defined in setup. + $this->items[$key] = $value; + } else { + $this->joinDefaults($key, $value); + } + } + } + + /** + * @return mixed + * @deprecated + */ + public function getLanguages() + { + return Grav::instance()['languages']; + } +} diff --git a/system/src/Grav/Common/Config/ConfigFileFinder.php b/system/src/Grav/Common/Config/ConfigFileFinder.php new file mode 100644 index 0000000..887575a --- /dev/null +++ b/system/src/Grav/Common/Config/ConfigFileFinder.php @@ -0,0 +1,258 @@ +base = $base ? "{$base}/" : ''; + + return $this; + } + + /** + * Return all locations for all the files with a timestamp. + * + * @param array $paths List of folders to look from. + * @param string $pattern Pattern to match the file. Pattern will also be removed from the key. + * @param int $levels Maximum number of recursive directories. + * @return array + */ + public function locateFiles(array $paths, $pattern = '|\.yaml$|', $levels = -1) + { + $list = []; + foreach ($paths as $folder) { + $list += $this->detectRecursive($folder, $pattern, $levels); + } + return $list; + } + + /** + * Return all locations for all the files with a timestamp. + * + * @param array $paths List of folders to look from. + * @param string $pattern Pattern to match the file. Pattern will also be removed from the key. + * @param int $levels Maximum number of recursive directories. + * @return array + */ + public function getFiles(array $paths, $pattern = '|\.yaml$|', $levels = -1) + { + $list = []; + foreach ($paths as $folder) { + $path = trim(Folder::getRelativePath($folder), '/'); + + $files = $this->detectRecursive($folder, $pattern, $levels); + + $list += $files[trim($path, '/')]; + } + return $list; + } + + /** + * Return all paths for all the files with a timestamp. + * + * @param array $paths List of folders to look from. + * @param string $pattern Pattern to match the file. Pattern will also be removed from the key. + * @param int $levels Maximum number of recursive directories. + * @return array + */ + public function listFiles(array $paths, $pattern = '|\.yaml$|', $levels = -1) + { + $list = []; + foreach ($paths as $folder) { + $list = array_merge_recursive($list, $this->detectAll($folder, $pattern, $levels)); + } + return $list; + } + + /** + * Find filename from a list of folders. + * + * Note: Only finds the last override. + * + * @param string $filename + * @param array $folders + * @return array + */ + public function locateFileInFolder($filename, array $folders) + { + $list = []; + foreach ($folders as $folder) { + $list += $this->detectInFolder($folder, $filename); + } + return $list; + } + + /** + * Find filename from a list of folders. + * + * @param array $folders + * @param string $filename + * @return array + */ + public function locateInFolders(array $folders, $filename = null) + { + $list = []; + foreach ($folders as $folder) { + $path = trim(Folder::getRelativePath($folder), '/'); + $list[$path] = $this->detectInFolder($folder, $filename); + } + return $list; + } + + /** + * Return all existing locations for a single file with a timestamp. + * + * @param array $paths Filesystem paths to look up from. + * @param string $name Configuration file to be located. + * @param string $ext File extension (optional, defaults to .yaml). + * @return array + */ + public function locateFile(array $paths, $name, $ext = '.yaml') + { + $filename = preg_replace('|[.\/]+|', '/', $name) . $ext; + + $list = []; + foreach ($paths as $folder) { + $path = trim(Folder::getRelativePath($folder), '/'); + + if (is_file("{$folder}/{$filename}")) { + $modified = filemtime("{$folder}/{$filename}"); + } else { + $modified = 0; + } + $basename = $this->base . $name; + $list[$path] = [$basename => ['file' => "{$path}/{$filename}", 'modified' => $modified]]; + } + + return $list; + } + + /** + * Detects all directories with a configuration file and returns them with last modification time. + * + * @param string $folder Location to look up from. + * @param string $pattern Pattern to match the file. Pattern will also be removed from the key. + * @param int $levels Maximum number of recursive directories. + * @return array + * @internal + */ + protected function detectRecursive($folder, $pattern, $levels) + { + $path = trim(Folder::getRelativePath($folder), '/'); + + if (is_dir($folder)) { + // Find all system and user configuration files. + $options = [ + 'levels' => $levels, + 'compare' => 'Filename', + 'pattern' => $pattern, + 'filters' => [ + 'pre-key' => $this->base, + 'key' => $pattern, + 'value' => function (\RecursiveDirectoryIterator $file) use ($path) { + return ['file' => "{$path}/{$file->getSubPathname()}", 'modified' => $file->getMTime()]; + } + ], + 'key' => 'SubPathname' + ]; + + $list = Folder::all($folder, $options); + + ksort($list); + } else { + $list = []; + } + + return [$path => $list]; + } + + /** + * Detects all directories with the lookup file and returns them with last modification time. + * + * @param string $folder Location to look up from. + * @param string $lookup Filename to be located (defaults to directory name). + * @return array + * @internal + */ + protected function detectInFolder($folder, $lookup = null) + { + $folder = rtrim($folder, '/'); + $path = trim(Folder::getRelativePath($folder), '/'); + $base = $path === $folder ? '' : ($path ? substr($folder, 0, -strlen($path)) : $folder . '/'); + + $list = []; + + if (is_dir($folder)) { + $iterator = new \DirectoryIterator($folder); + + /** @var \DirectoryIterator $directory */ + foreach ($iterator as $directory) { + if (!$directory->isDir() || $directory->isDot()) { + continue; + } + + $name = $directory->getBasename(); + $find = ($lookup ?: $name) . '.yaml'; + $filename = "{$path}/{$name}/{$find}"; + + if (file_exists($base . $filename)) { + $basename = $this->base . $name; + $list[$basename] = ['file' => $filename, 'modified' => filemtime($base . $filename)]; + } + } + } + + return $list; + } + + /** + * Detects all plugins with a configuration file and returns them with last modification time. + * + * @param string $folder Location to look up from. + * @param string $pattern Pattern to match the file. Pattern will also be removed from the key. + * @param int $levels Maximum number of recursive directories. + * @return array + * @internal + */ + protected function detectAll($folder, $pattern, $levels) + { + $path = trim(Folder::getRelativePath($folder), '/'); + + if (is_dir($folder)) { + // Find all system and user configuration files. + $options = [ + 'levels' => $levels, + 'compare' => 'Filename', + 'pattern' => $pattern, + 'filters' => [ + 'pre-key' => $this->base, + 'key' => $pattern, + 'value' => function (\RecursiveDirectoryIterator $file) use ($path) { + return ["{$path}/{$file->getSubPathname()}" => $file->getMTime()]; + } + ], + 'key' => 'SubPathname' + ]; + + $list = Folder::all($folder, $options); + + ksort($list); + } else { + $list = []; + } + + return $list; + } +} diff --git a/system/src/Grav/Common/Config/Languages.php b/system/src/Grav/Common/Config/Languages.php new file mode 100644 index 0000000..4d92d87 --- /dev/null +++ b/system/src/Grav/Common/Config/Languages.php @@ -0,0 +1,44 @@ +checksum = $checksum; + } + + return $this->checksum; + } + + public function modified($modified = null) + { + if ($modified !== null) { + $this->modified = $modified; + } + + return $this->modified; + } + + public function reformat() + { + if (isset($this->items['plugins'])) { + $this->items = array_merge_recursive($this->items, $this->items['plugins']); + unset($this->items['plugins']); + } + } + + public function mergeRecursive(array $data) + { + $this->items = array_merge_recursive($this->items, $data); + } +} diff --git a/system/src/Grav/Common/Config/Setup.php b/system/src/Grav/Common/Config/Setup.php new file mode 100644 index 0000000..8b6b707 --- /dev/null +++ b/system/src/Grav/Common/Config/Setup.php @@ -0,0 +1,254 @@ + [ + 'type' => 'ReadOnlyStream', + 'prefixes' => [ + '' => ['system'], + ] + ], + 'user' => [ + 'type' => 'ReadOnlyStream', + 'prefixes' => [ + '' => ['user'], + ] + ], + 'environment' => [ + 'type' => 'ReadOnlyStream' + // If not defined, environment will be set up in the constructor. + ], + 'asset' => [ + 'type' => 'ReadOnlyStream', + 'prefixes' => [ + '' => ['assets'], + ] + ], + 'blueprints' => [ + 'type' => 'ReadOnlyStream', + 'prefixes' => [ + '' => ['environment://blueprints', 'user://blueprints', 'system/blueprints'], + ] + ], + 'config' => [ + 'type' => 'ReadOnlyStream', + 'prefixes' => [ + '' => ['environment://config', 'user://config', 'system/config'], + ] + ], + 'plugins' => [ + 'type' => 'ReadOnlyStream', + 'prefixes' => [ + '' => ['user://plugins'], + ] + ], + 'plugin' => [ + 'type' => 'ReadOnlyStream', + 'prefixes' => [ + '' => ['user://plugins'], + ] + ], + 'themes' => [ + 'type' => 'ReadOnlyStream', + 'prefixes' => [ + '' => ['user://themes'], + ] + ], + 'languages' => [ + 'type' => 'ReadOnlyStream', + 'prefixes' => [ + '' => ['environment://languages', 'user://languages', 'system/languages'], + ] + ], + 'cache' => [ + 'type' => 'Stream', + 'prefixes' => [ + '' => ['cache'], + 'images' => ['images'] + ] + ], + 'log' => [ + 'type' => 'Stream', + 'prefixes' => [ + '' => ['logs'] + ] + ], + 'backup' => [ + 'type' => 'Stream', + 'prefixes' => [ + '' => ['backup'] + ] + ], + 'image' => [ + 'type' => 'ReadOnlyStream', + 'prefixes' => [ + '' => ['user://images', 'system://images'] + ] + ], + 'page' => [ + 'type' => 'ReadOnlyStream', + 'prefixes' => [ + '' => ['user://pages'] + ] + ], + 'account' => [ + 'type' => 'ReadOnlyStream', + 'prefixes' => [ + '' => ['user://accounts'] + ] + ], + ]; + + 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'; + $setup = is_file($file) ? (array) include $file : []; + + // Add default streams defined in beginning of the class. + if (!isset($setup['streams']['schemes'])) { + $setup['streams']['schemes'] = []; + } + $setup['streams']['schemes'] += $this->streams; + + // Initialize class. + parent::__construct($setup); + + // Set up environment. + $this->def('environment', $environment); + $this->def('streams.schemes.environment.prefixes', ['' => ["user://{$this->environment}"]]); + } + + /** + * @return $this + */ + public function init() + { + $locator = new UniformResourceLocator(GRAV_ROOT); + $files = []; + + $guard = 5; + do { + $check = $files; + $this->initializeLocator($locator); + $files = $locator->findResources('config://streams.yaml'); + + if ($check === $files) { + break; + } + + // Update streams. + foreach (array_reverse($files) as $path) { + $file = CompiledYamlFile::instance($path); + $content = $file->content(); + if (!empty($content['schemes'])) { + $this->items['streams']['schemes'] = $content['schemes'] + $this->items['streams']['schemes']; + } + } + } while (--$guard); + + if (!$guard) { + throw new \RuntimeException('Setup: Configuration reload loop detected!'); + } + + // Make sure we have valid setup. + $this->check($locator); + + return $this; + } + + /** + * Initialize resource locator by using the configuration. + * + * @param UniformResourceLocator $locator + */ + public function initializeLocator(UniformResourceLocator $locator) + { + $locator->reset(); + + $schemes = (array) $this->get('streams.schemes', []); + + foreach ($schemes as $scheme => $config) { + if (isset($config['paths'])) { + $locator->addPath($scheme, '', $config['paths']); + } + if (isset($config['prefixes'])) { + foreach ($config['prefixes'] as $prefix => $paths) { + $locator->addPath($scheme, $prefix, $paths); + } + } + } + } + + /** + * Get available streams and their types from the configuration. + * + * @return array + */ + public function getStreams() + { + $schemes = []; + foreach ((array) $this->get('streams.schemes') as $scheme => $config) { + $type = !empty($config['type']) ? $config['type'] : 'ReadOnlyStream'; + if ($type[0] != '\\') { + $type = '\\RocketTheme\\Toolbox\\StreamWrapper\\' . $type; + } + + $schemes[$scheme] = $type; + } + + return $schemes; + } + + /** + * @param UniformResourceLocator $locator + * @throws \InvalidArgumentException + */ + protected function check(UniformResourceLocator $locator) + { + $streams = isset($this->items['streams']['schemes']) ? $this->items['streams']['schemes'] : null; + if (!is_array($streams)) { + throw new \InvalidArgumentException('Configuration is missing streams.schemes!'); + } + $diff = array_keys(array_diff_key($this->streams, $streams)); + if ($diff) { + throw new \InvalidArgumentException( + sprintf('Configuration is missing keys %s from streams.schemes!', implode(', ', $diff)) + ); + } + + if (!$locator->findResource('environment://config', true)) { + // If environment does not have its own directory, remove it from the lookup. + $this->set('streams.schemes.environment.prefixes', ['config' => []]); + $this->initializeLocator($locator); + } + + // Create security.yaml if it doesn't exist. + $filename = $locator->findResource('config://security.yaml', true, true); + $file = YamlFile::instance($filename); + if (!$file->exists()) { + $file->save(['salt' => Utils::generateRandomString(14)]); + $file->free(); + } + } +} diff --git a/system/src/Grav/Common/Data/Blueprint.php b/system/src/Grav/Common/Data/Blueprint.php new file mode 100644 index 0000000..68de5de --- /dev/null +++ b/system/src/Grav/Common/Data/Blueprint.php @@ -0,0 +1,476 @@ + 1]; + + /** + * @param string $name + * @param array $data + * @param Blueprints $context + */ + public function __construct($name, array $data = array(), Blueprints $context = null) + { + $this->name = $name; + $this->items = $data; + $this->context = $context; + } + + /** + * Set filter for inherited properties. + * + * @param array $filter List of field names to be inherited. + */ + public function setFilter(array $filter) + { + $this->filter = array_flip($filter); + } + + /** + * Return all form fields. + * + * @return array + */ + public function fields() + { + if (!isset($this->fields)) { + $this->fields = []; + $this->embed('', $this->items); + } + + return $this->fields; + } + + /** + * Validate data against blueprints. + * + * @param array $data + * @throws \RuntimeException + */ + public function validate(array $data) + { + // Initialize data + $this->fields(); + + try { + $this->validateArray($data, $this->nested); + } catch (\RuntimeException $e) { + $language = self::getGrav()['language']; + $message = sprintf($language->translate('FORM.VALIDATION_FAIL', null, true) . ' %s', $e->getMessage()); + throw new \RuntimeException($message); + } + } + + /** + * Merge two arrays by using blueprints. + * + * @param array $data1 + * @param array $data2 + * @return array + */ + public function mergeData(array $data1, array $data2) + { + // Initialize data + $this->fields(); + return $this->mergeArrays($data1, $data2, $this->nested); + } + + /** + * Filter data by using blueprints. + * + * @param array $data + * @return array + */ + public function filter(array $data) + { + // Initialize data + $this->fields(); + return $this->filterArray($data, $this->nested); + } + + /** + * Return data fields that do not exist in blueprints. + * + * @param array $data + * @param string $prefix + * @return array + */ + public function extra(array $data, $prefix = '') + { + // Initialize data + $this->fields(); + $rules = $this->nested; + + // Drill down to prefix level + if (!empty($prefix)) { + $parts = explode('.', trim($prefix, '.')); + foreach ($parts as $part) { + $rules = isset($rules[$part]) ? $rules[$part] : []; + } + } + + return $this->extraArray($data, $rules, $prefix); + } + + /** + * Extend blueprint with another blueprint. + * + * @param Blueprint $extends + * @param bool $append + */ + public function extend(Blueprint $extends, $append = false) + { + $blueprints = $append ? $this->items : $extends->toArray(); + $appended = $append ? $extends->toArray() : $this->items; + + $bref_stack = array(&$blueprints); + $head_stack = array($appended); + + do { + end($bref_stack); + + $bref = &$bref_stack[key($bref_stack)]; + $head = array_pop($head_stack); + + unset($bref_stack[key($bref_stack)]); + + foreach (array_keys($head) as $key) { + if (isset($key, $bref[$key]) && is_array($bref[$key]) && is_array($head[$key])) { + $bref_stack[] = &$bref[$key]; + $head_stack[] = $head[$key]; + } else { + $bref = array_merge($bref, array($key => $head[$key])); + } + } + } while (count($head_stack)); + + $this->items = $blueprints; + } + + /** + * Convert object into an array. + * + * @return array + */ + public function getState() + { + return ['name' => $this->name, 'items' => $this->items, 'rules' => $this->rules, 'nested' => $this->nested]; + } + + /** + * Embed an array to the blueprint. + * + * @param $name + * @param array $value + * @param string $separator + */ + public function embed($name, array $value, $separator = '.') + { + + if (!isset($value['form']['fields']) || !is_array($value['form']['fields'])) { + return; + } + // Initialize data + $this->fields(); + $prefix = $name ? strtr($name, $separator, '.') . '.' : ''; + $params = array_intersect_key($this->filter, $value); + $this->parseFormFields($value['form']['fields'], $params, $prefix, $this->fields); + } + + /** + * @param array $data + * @param array $rules + * @throws \RuntimeException + * @internal + */ + protected function validateArray(array $data, array $rules) + { + $this->checkRequired($data, $rules); + + foreach ($data as $key => $field) { + $val = isset($rules[$key]) ? $rules[$key] : null; + $rule = is_string($val) ? $this->rules[$val] : null; + + if ($rule) { + // Item has been defined in blueprints. + Validation::validate($field, $rule); + } elseif (is_array($field) && is_array($val)) { + // Array has been defined in blueprints. + $this->validateArray($field, $val); + } elseif (isset($this->items['form']['validation']) && $this->items['form']['validation'] == 'strict') { + // Undefined/extra item. + throw new \RuntimeException(sprintf('%s is not defined in blueprints', $key)); + } + } + } + + /** + * @param array $data + * @param array $rules + * @return array + * @internal + */ + protected function filterArray(array $data, array $rules) + { + $results = array(); + foreach ($data as $key => $field) { + $val = isset($rules[$key]) ? $rules[$key] : null; + $rule = is_string($val) ? $this->rules[$val] : null; + + if ($rule) { + // Item has been defined in blueprints. + $field = Validation::filter($field, $rule); + } elseif (is_array($field) && is_array($val)) { + // Array has been defined in blueprints. + $field = $this->filterArray($field, $val); + } elseif (isset($this->items['form']['validation']) && $this->items['form']['validation'] == 'strict') { + $field = null; + } + + if (isset($field) && (!is_array($field) || !empty($field))) { + $results[$key] = $field; + } + } + + return $results; + } + + /** + * @param array $data1 + * @param array $data2 + * @param array $rules + * @return array + * @internal + */ + protected function mergeArrays(array $data1, array $data2, array $rules) + { + foreach ($data2 as $key => $field) { + $val = isset($rules[$key]) ? $rules[$key] : null; + $rule = is_string($val) ? $this->rules[$val] : null; + + if (!$rule && array_key_exists($key, $data1) && is_array($field) && is_array($val)) { + // Array has been defined in blueprints. + $data1[$key] = $this->mergeArrays($data1[$key], $field, $val); + } else { + // Otherwise just take value from the data2. + $data1[$key] = $field; + } + } + + return $data1; + } + + /** + * @param array $data + * @param array $rules + * @param string $prefix + * @return array + * @internal + */ + protected function extraArray(array $data, array $rules, $prefix) + { + $array = array(); + foreach ($data as $key => $field) { + $val = isset($rules[$key]) ? $rules[$key] : null; + $rule = is_string($val) ? $this->rules[$val] : null; + + if ($rule) { + // Item has been defined in blueprints. + } elseif (is_array($field) && is_array($val)) { + // Array has been defined in blueprints. + $array += $this->ExtraArray($field, $val, $prefix . $key . '.'); + } else { + // Undefined/extra item. + $array[$prefix.$key] = $field; + } + } + return $array; + } + + /** + * Gets all field definitions from the blueprints. + * + * @param array $fields + * @param array $params + * @param string $prefix + * @param array $current + * @internal + */ + protected function parseFormFields(array &$fields, $params, $prefix, array &$current) + { + // Go though all the fields in current level. + foreach ($fields as $key => &$field) { + $current[$key] = &$field; + // Set name from the array key. + $field['name'] = $prefix . $key; + $field += $params; + + if (isset($field['fields']) && (!isset($field['type']) || $field['type'] !== 'list')) { + // Recursively get all the nested fields. + $newParams = array_intersect_key($this->filter, $field); + $this->parseFormFields($field['fields'], $newParams, $prefix, $current[$key]['fields']); + } else if ($field['type'] !== 'ignore') { + $this->rules[$prefix . $key] = &$field; + $this->addProperty($prefix . $key); + + if ($field['type'] === 'list') { + // we loop through list to get the actual field + if (isset($field['fields']) && $field['fields']) { + foreach($field['fields'] as $subName => &$subField) { + $this->parseFormField($subField); + } + } + } else { + $this->parseFormField($field); + } + + if (isset($field['validate']['rule']) && $field['type'] !== 'ignore') { + $field['validate'] += $this->getRule($field['validate']['rule']); + } + } + } + } + /** + * Parses individual field definition + * + * @param array $field + * @internal + */ + protected function parseFormField(&$field) { + foreach ($field as $name => $value) { + // Support nested blueprints. + if ($this->context && $name == '@import') { + $values = (array) $value; + if (!isset($field['fields'])) { + $field['fields'] = array(); + } + foreach ($values as $bname) { + $b = $this->context->get($bname); + $field['fields'] = array_merge($field['fields'], $b->fields()); + } + } + + // Support for callable data values. + elseif (substr($name, 0, 6) == '@data-') { + $property = substr($name, 6); + if (is_array($value)) { + $func = array_shift($value); + } else { + $func = $value; + $value = array(); + } + list($o, $f) = preg_split('/::/', $func); + if (!$f && function_exists($o)) { + $data = call_user_func_array($o, $value); + } elseif ($f && method_exists($o, $f)) { + $data = call_user_func_array(array($o, $f), $value); + } + + // If function returns a value, + if (isset($data)) { + if (isset($field[$property]) && is_array($field[$property]) && is_array($data)) { + // Combine field and @data-field together. + $field[$property] += $data; + } else { + // Or create/replace field with @data-field. + $field[$property] = $data; + } + } + } + + elseif (substr($name, 0, 8) == '@config-') { + $property = substr($name, 8); + $default = isset($field[$property]) ? $field[$property] : null; + $config = self::getGrav()['config']->get($value, $default); + + if (!is_null($config)) { + $field[$property] = $config; + } + } + } + } + + /** + * Add property to the definition. + * + * @param string $path Comma separated path to the property. + * @internal + */ + protected function addProperty($path) + { + $parts = explode('.', $path); + $item = array_pop($parts); + + $nested = &$this->nested; + foreach ($parts as $part) { + if (!isset($nested[$part])) { + $nested[$part] = array(); + } + $nested = &$nested[$part]; + } + + if (!isset($nested[$item])) { + $nested[$item] = $path; + } + } + + /** + * @param $rule + * @return array + * @internal + */ + protected function getRule($rule) + { + if (isset($this->items['rules'][$rule]) && is_array($this->items['rules'][$rule])) { + return $this->items['rules'][$rule]; + } + return array(); + } + + /** + * @param array $data + * @param array $fields + * @throws \RuntimeException + * @internal + */ + protected function checkRequired(array $data, array $fields) + { + foreach ($fields as $name => $field) { + if (!is_string($field)) { + continue; + } + $field = $this->rules[$field]; + if (isset($field['validate']['required']) + && $field['validate']['required'] === true + && empty($data[$name])) { + $value = isset($field['label']) ? $field['label'] : $field['name']; + $language = self::getGrav()['language']; + $message = sprintf($language->translate('FORM.MISSING_REQUIRED_FIELD', null, true) . ' %s', $value); + throw new \RuntimeException($message); + } + } + } +} diff --git a/system/src/Grav/Common/Data/Blueprints.php b/system/src/Grav/Common/Data/Blueprints.php new file mode 100644 index 0000000..4c144a3 --- /dev/null +++ b/system/src/Grav/Common/Data/Blueprints.php @@ -0,0 +1,145 @@ +search = $search; + } + + /** + * Get blueprint. + * + * @param string $type Blueprint type. + * @return Blueprint + * @throws \RuntimeException + */ + public function get($type) + { + if (!isset($this->instances[$type])) { + $parents = []; + if (is_string($this->search)) { + $filename = $this->search . $type . YAML_EXT; + + // Check if search is a stream and resolve the path. + if (strpos($filename, '://')) { + $grav = static::getGrav(); + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + $parents = $locator->findResources($filename); + $filename = array_shift($parents); + } + } else { + $filename = isset($this->search[$type]) ? $this->search[$type] : ''; + } + + if ($filename && is_file($filename)) { + $file = CompiledYamlFile::instance($filename); + $blueprints = $file->content(); + } else { + $blueprints = []; + } + + $blueprint = new Blueprint($type, $blueprints, $this); + + if (isset($blueprints['@extends'])) { + // Extend blueprint by other blueprints. + $extends = (array) $blueprints['@extends']; + + if (is_string(key($extends))) { + $extends = [ $extends ]; + } + + foreach ($extends as $extendConfig) { + $extendType = !is_string($extendConfig) ? empty($extendConfig['type']) ? false : $extendConfig['type'] : $extendConfig; + + if (!$extendType) { + continue; + } elseif ($extendType === '@parent') { + $parentFile = array_shift($parents); + if (!$parentFile || !is_file($parentFile)) { + continue; + } + $blueprints = CompiledYamlFile::instance($parentFile)->content(); + $parent = new Blueprint($type.'-parent', $blueprints, $this); + $blueprint->extend($parent); + continue; + } + + if (is_string($extendConfig) || empty($extendConfig['context'])) { + $context = $this; + } else { + // Load blueprints from external context. + $array = explode('://', $extendConfig['context'], 2); + $scheme = array_shift($array); + $path = array_shift($array); + if ($path) { + $scheme .= '://'; + $extendType = $path ? "{$path}/{$extendType}" : $extendType; + } + $context = new self($scheme); + } + $blueprint->extend($context->get($extendType)); + } + } + + $this->instances[$type] = $blueprint; + } + + return $this->instances[$type]; + } + + /** + * Get all available blueprint types. + * + * @return array List of type=>name + */ + public function types() + { + if ($this->types === null) { + $this->types = array(); + + // Check if search is a stream. + if (strpos($this->search, '://')) { + // Stream: use UniformResourceIterator. + $grav = static::getGrav(); + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + $iterator = $locator->getIterator($this->search, null); + } else { + // Not a stream: use DirectoryIterator. + $iterator = new \DirectoryIterator($this->search); + } + + /** @var \DirectoryIterator $file */ + foreach ($iterator as $file) { + if (!$file->isFile() || '.' . $file->getExtension() != YAML_EXT) { + continue; + } + $name = $file->getBasename(YAML_EXT); + $this->types[$name] = ucfirst(strtr($name, '_', ' ')); + } + } + return $this->types; + } +} diff --git a/system/src/Grav/Common/Data/Data.php b/system/src/Grav/Common/Data/Data.php new file mode 100644 index 0000000..0a08507 --- /dev/null +++ b/system/src/Grav/Common/Data/Data.php @@ -0,0 +1,282 @@ +items = $items; + $this->blueprints = $blueprints; + } + + /** + * Get value by using dot notation for nested arrays/objects. + * + * @example $value = $data->value('this.is.my.nested.variable'); + * + * @param string $name Dot separated path to the requested value. + * @param mixed $default Default value (or null). + * @param string $separator Separator, defaults to '.' + * @return mixed Value. + */ + public function value($name, $default = null, $separator = '.') + { + return $this->get($name, $default, $separator); + } + + /** + * Join nested values together by using blueprints. + * + * @param string $name Dot separated path to the requested value. + * @param mixed $value Value to be joined. + * @param string $separator Separator, defaults to '.' + * @return $this + * @throws \RuntimeException + */ + public function join($name, $value, $separator = '.') + { + $old = $this->get($name, null, $separator); + if ($old !== null) { + if (!is_array($old)) { + throw new \RuntimeException('Value ' . $old); + } + if (is_object($value)) { + $value = (array) $value; + } elseif (!is_array($value)) { + throw new \RuntimeException('Value ' . $value); + } + $value = $this->blueprints()->mergeData($old, $value, $name, $separator); + } + + $this->set($name, $value, $separator); + + return $this; + } + + /** + * Get nested structure containing default values defined in the blueprints. + * + * Fields without default value are ignored in the list. + + * @return array + */ + public function getDefaults() + { + return $this->blueprints()->getDefaults(); + } + + /** + * Set default values by using blueprints. + * + * @param string $name Dot separated path to the requested value. + * @param mixed $value Value to be joined. + * @param string $separator Separator, defaults to '.' + * @return $this + */ + public function joinDefaults($name, $value, $separator = '.') + { + if (is_object($value)) { + $value = (array) $value; + } + $old = $this->get($name, null, $separator); + if ($old !== null) { + $value = $this->blueprints()->mergeData($value, $old, $name, $separator); + } + + $this->set($name, $value, $separator); + + return $this; + } + + /** + * Get value from the configuration and join it with given data. + * + * @param string $name Dot separated path to the requested value. + * @param array $value Value to be joined. + * @param string $separator Separator, defaults to '.' + * @return array + * @throws \RuntimeException + */ + public function getJoined($name, $value, $separator = '.') + { + if (is_object($value)) { + $value = (array) $value; + } elseif (!is_array($value)) { + throw new \RuntimeException('Value ' . $value); + } + + $old = $this->get($name, null, $separator); + + if ($old === null) { + // No value set; no need to join data. + return $value; + } + + if (!is_array($old)) { + throw new \RuntimeException('Value ' . $old); + } + + // Return joined data. + return $this->blueprints()->mergeData($old, $value, $name, $separator); + } + + + /** + * Merge two configurations together. + * + * @param array $data + * @return $this + */ + public function merge(array $data) + { + $this->items = $this->blueprints()->mergeData($this->items, $data); + + return $this; + } + + /** + * Set default values to the configuration if variables were not set. + * + * @param array $data + * @return $this + */ + public function setDefaults(array $data) + { + $this->items = $this->blueprints()->mergeData($data, $this->items); + + return $this; + } + + /** + * Validate by blueprints. + * + * @return $this + * @throws \Exception + */ + public function validate() + { + $this->blueprints()->validate($this->items); + + return $this; + } + + /** + * @return $this + * Filter all items by using blueprints. + */ + public function filter() + { + $this->items = $this->blueprints()->filter($this->items); + + return $this; + } + + /** + * Get extra items which haven't been defined in blueprints. + * + * @return array + */ + public function extra() + { + return $this->blueprints ? $this->blueprints->extra($this->items) : array(); + } + + /** + * Return blueprints. + * + * @return Blueprints + */ + public function blueprints() + { + if (!$this->blueprints){ + $this->blueprints = new Blueprints; + } elseif (is_callable($this->blueprints)) { + // Lazy load blueprints. + $blueprints = $this->blueprints; + $this->blueprints = $blueprints(); + } + return $this->blueprints; + } + + /** + * Save data if storage has been defined. + */ + public function save() + { + $file = $this->file(); + if ($file) { + $file->save($this->items); + } + } + + /** + * Returns whether the data already exists in the storage. + * + * NOTE: This method does not check if the data is current. + * + * @return bool + */ + public function exists() + { + return $this->file()->exists(); + } + + /** + * Return unmodified data as raw string. + * + * NOTE: This function only returns data which has been saved to the storage. + * + * @return string + */ + public function raw() + { + return $this->file()->raw(); + } + + /** + * Set or get the data storage. + * + * @param FileInterface $storage Optionally enter a new storage. + * @return FileInterface + */ + public function file(FileInterface $storage = null) + { + if ($storage) { + $this->storage = $storage; + } + return $this->storage; + } +} diff --git a/system/src/Grav/Common/Data/DataInterface.php b/system/src/Grav/Common/Data/DataInterface.php new file mode 100644 index 0000000..85d2fb6 --- /dev/null +++ b/system/src/Grav/Common/Data/DataInterface.php @@ -0,0 +1,68 @@ +value('this.is.my.nested.variable'); + * + * @param string $name Dot separated path to the requested value. + * @param mixed $default Default value (or null). + * @param string $separator Separator, defaults to '.' + * @return mixed Value. + */ + public function value($name, $default = null, $separator = '.'); + + /** + * Merge external data. + * + * @param array $data + * @return mixed + */ + public function merge(array $data); + + /** + * Return blueprints. + */ + public function blueprints(); + + /** + * Validate by blueprints. + * + * @throws \Exception + */ + public function validate(); + + /** + * Filter all items by using blueprints. + */ + public function filter(); + + /** + * Get extra items which haven't been defined in blueprints. + */ + public function extra(); + + /** + * Save data into the file. + */ + public function save(); + + /** + * Set or get the data storage. + * + * @param FileInterface $storage Optionally enter a new storage. + * @return FileInterface + */ + public function file(FileInterface $storage = null); +} diff --git a/system/src/Grav/Common/Data/Validation.php b/system/src/Grav/Common/Data/Validation.php new file mode 100644 index 0000000..059ea92 --- /dev/null +++ b/system/src/Grav/Common/Data/Validation.php @@ -0,0 +1,713 @@ +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); + } else { + $success = self::typeText($value, $validate, $field); + } + if (!$success) { + throw new \RuntimeException($message); + } + + // Check individual rules + foreach ($validate as $rule => $params) { + $method = 'validate'.strtr($rule, '-', '_'); + if (method_exists(__CLASS__, $method)) { + $success = self::$method($value, $params); + + if (!$success) { + throw new \RuntimeException($message); + } + } + } + } + + /** + * Filter value against a blueprint field definition. + * + * @param mixed $value + * @param array $field + * @return mixed Filtered value. + */ + public static function filter($value, array $field) + { + $validate = isset($field['validate']) ? (array) $field['validate'] : array(); + + // If value isn't required, we will return null if empty value is given. + if (empty($validate['required']) && ($value === null || $value === '')) { + return null; + } + + // 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 { + $yaml = new Parser(); + return $yaml->parse($value); + } catch (ParseException $e) { + throw new \RuntimeException($e->getMessage()); + } + } + + // Validate type with fallback type text. + $type = (string) isset($field['validate']['type']) ? $field['validate']['type'] : $field['type']; + $method = 'filter'.strtr($type, '-', '_'); + if (method_exists(__CLASS__, $method)) { + $value = self::$method($value, $validate, $field); + } else { + $value = self::filterText($value, $validate, $field); + } + + return $value; + } + + /** + * HTML5 input: text + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeText($value, array $params, array $field) + { + if (!is_string($value)) { + return false; + } + + if (isset($params['min']) && strlen($value) < $params['min']) { + return false; + } + + if (isset($params['max']) && strlen($value) > $params['max']) { + return false; + } + + $min = isset($params['min']) ? $params['min'] : 0; + if (isset($params['step']) && (strlen($value) - $min) % $params['step'] == 0) { + return false; + } + + if ((!isset($params['multiline']) || !$params['multiline']) && preg_match('/\R/um', $value)) { + return false; + } + + return true; + } + + protected static function filterText($value, array $params, array $field) + { + return (string) $value; + } + + protected static function filterCommaList($value, array $params, array $field) + { + return is_array($value) ? $value : preg_split('/\s*,\s*/', $value, -1, PREG_SPLIT_NO_EMPTY); + } + + protected static function typeCommaList($value, array $params, array $field) + { + return is_array($value) ? true : self::typeText($value, $params, $field); + } + + /** + * HTML5 input: textarea + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeTextarea($value, array $params, array $field) + { + if (!isset($params['multiline'])) { + $params['multiline'] = true; + } + + return self::typeText($value, $params, $field); + } + + /** + * HTML5 input: password + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typePassword($value, array $params, array $field) + { + return self::typeText($value, $params, $field); + } + + /** + * HTML5 input: hidden + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeHidden($value, array $params, array $field) + { + return self::typeText($value, $params, $field); + } + + /** + * Custom input: checkbox list + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeCheckboxes($value, array $params, array $field) + { + return self::typeArray((array) $value, $params, $field); + } + + protected static function filterCheckboxes($value, array $params, array $field) + { + return self::filterArray($value, $params, $field); + } + + /** + * HTML5 input: checkbox + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeCheckbox($value, array $params, array $field) + { + $value = (string) $value; + + if (!isset($field['value'])) { + $field['value'] = 1; + } + if ($value && $value != $field['value']) { + return false; + } + + return true; + } + + /** + * HTML5 input: radio + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeRadio($value, array $params, array $field) + { + return self::typeArray((array) $value, $params, $field); + } + + /** + * Custom input: toggle + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeToggle($value, array $params, array $field) + { + return self::typeArray((array) $value, $params, $field); + } + + /** + * Custom input: file + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeFile($value, array $params, array $field) + { + return self::typeArray((array) $value, $params, $field); + } + + protected static function filterFile($value, array $params, array $field) + { + if (isset($field['multiple']) && $field['multiple'] === true) { + return (array) $value; + } + + if (is_array($value)) { + return reset($value); + } + + return $value; + } + + /** + * HTML5 input: select + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeSelect($value, array $params, array $field) + { + return self::typeArray((array) $value, $params, $field); + } + + /** + * HTML5 input: number + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + + public static function typeNumber($value, array $params, array $field) + { + if (!is_numeric($value)) { + return false; + } + + if (isset($params['min']) && $value < $params['min']) { + return false; + } + + if (isset($params['max']) && $value > $params['max']) { + return false; + } + + $min = isset($params['min']) ? $params['min'] : 0; + if (isset($params['step']) && fmod($value - $min, $params['step']) == 0) { + return false; + } + + return true; + } + + protected static function filterNumber($value, array $params, array $field) + { + return (int) $value; + } + + protected static function filterDateTime($value, array $params, array $field) + { + $format = self::getGrav()['config']->get('system.pages.dateformat.default'); + if ($format) { + $converted = new \DateTime($value); + return $converted->format($format); + } + return $value; + } + + + /** + * HTML5 input: range + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeRange($value, array $params, array $field) + { + return self::typeNumber($value, $params, $field); + } + + protected static function filterRange($value, array $params, array $field) + { + return self::filterNumber($value, $params, $field); + } + + /** + * HTML5 input: color + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeColor($value, array $params, array $field) + { + return preg_match('/^\#[0-9a-fA-F]{3}[0-9a-fA-F]{3}?$/u', $value); + } + + /** + * HTML5 input: email + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeEmail($value, array $params, array $field) + { + return self::typeText($value, $params, $field) && filter_var($value, FILTER_VALIDATE_EMAIL); + } + + /** + * HTML5 input: url + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + + public static function typeUrl($value, array $params, array $field) + { + return self::typeText($value, $params, $field) && filter_var($value, FILTER_VALIDATE_URL); + } + + /** + * HTML5 input: datetime + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeDatetime($value, array $params, array $field) + { + if ($value instanceof \DateTime) { + return true; + } elseif (!is_string($value)) { + return false; + } elseif (!isset($params['format'])) { + return false !== strtotime($value); + } + + $dateFromFormat = \DateTime::createFromFormat($params['format'], $value); + + return $dateFromFormat && $value === date($params['format'], $dateFromFormat->getTimestamp()); + } + + /** + * HTML5 input: datetime-local + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeDatetimeLocal($value, array $params, array $field) + { + return self::typeDatetime($value, $params, $field); + } + + /** + * HTML5 input: date + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeDate($value, array $params, array $field) + { + $params = array($params); + if (!isset($params['format'])) { + $params['format'] = 'Y-m-d'; + } + return self::typeDatetime($value, $params, $field); + } + + /** + * HTML5 input: time + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeTime($value, array $params, array $field) + { + $params = array($params); + if (!isset($params['format'])) { + $params['format'] = 'H:i'; + } + return self::typeDatetime($value, $params, $field); + } + + /** + * HTML5 input: month + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeMonth($value, array $params, array $field) + { + $params = array($params); + if (!isset($params['format'])) { + $params['format'] = 'Y-m'; + } + return self::typeDatetime($value, $params, $field); + } + + /** + * HTML5 input: week + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeWeek($value, array $params, array $field) + { + if (!isset($params['format']) && !preg_match('/^\d{4}-W\d{2}$/u', $value)) { + return false; + } + return self::typeDatetime($value, $params, $field); + } + + /** + * Custom input: array + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeArray($value, array $params, array $field) + { + if (!is_array($value)) { + return false; + } + + if (isset($field['multiple'])) { + if (isset($params['min']) && count($value) < $params['min']) { + return false; + } + + if (isset($params['max']) && count($value) > $params['max']) { + return false; + } + + $min = isset($params['min']) ? $params['min'] : 0; + if (isset($params['step']) && (count($value) - $min) % $params['step'] == 0) { + return false; + } + } + + $options = isset($field['options']) ? array_keys($field['options']) : array(); + $values = isset($field['use']) && $field['use'] == 'keys' ? array_keys($value) : $value; + if ($options && array_diff($values, $options)) { + return false; + } + + return true; + } + + protected static function filterArray($value, $params, $field) + { + $values = (array) $value; + $options = isset($field['options']) ? array_keys($field['options']) : array(); + $multi = isset($field['multiple']) ? $field['multiple'] : false; + + if (count($values) == 1 && isset($values[0]) && $values[0] == '') { + return null; + } + + if ($options) { + $useKey = isset($field['use']) && $field['use'] == 'keys'; + foreach ($values as $key => $value) { + $values[$key] = $useKey ? (bool) $value : $value; + } + } + + if ($multi) { + foreach ($values as $key => $value) { + if (is_array($value)) { + $value = implode(',', $value); + } + + $values[$key] = array_map('trim', explode(',', $value)); + } + } + + return $values; + } + + public static function typeList($value, array $params, array $field) + { + if (!is_array($value)) { + return false; + } + + if (isset($field['fields'])) { + foreach ($value as $key => $item) { + foreach ($field['fields'] as $subKey => $subField) { + $subKey = trim($subKey, '.'); + $subValue = isset($item[$subKey]) ? $item[$subKey] : null; + self::validate($subValue, $subField); + } + } + } + + return true; + } + + protected static function filterList($value, array $params, array $field) + { + return (array) $value; + } + + /** + * Custom input: ignore (will not validate) + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeIgnore($value, array $params, array $field) + { + return true; + } + + public static function filterIgnore($value, array $params, array $field) + { + return $value; + } + + // HTML5 attributes (min, max and range are handled inside the types) + + public static function validateRequired($value, $params) + { + if (is_string($value)) { + $value = trim($value); + } + + return (bool) $params !== true || !empty($value); + } + + public static function validatePattern($value, $params) + { + return (bool) preg_match("`^{$params}$`u", $value); + } + + + // Internal types + + public static function validateAlpha($value, $params) + { + return ctype_alpha($value); + } + + public static function validateAlnum($value, $params) + { + return ctype_alnum($value); + } + + public static function typeBool($value, $params) + { + return is_bool($value) || $value == 1 || $value == 0; + } + + public static function validateBool($value, $params) + { + return is_bool($value) || $value == 1 || $value == 0; + } + + protected static function filterBool($value, $params) + { + return (bool) $value; + } + + public static function validateDigit($value, $params) + { + return ctype_digit($value); + } + + public static function validateFloat($value, $params) + { + return is_float(filter_var($value, FILTER_VALIDATE_FLOAT)); + } + + protected static function filterFloat($value, $params) + { + return (float) $value; + } + + public static function validateHex($value, $params) + { + return ctype_xdigit($value); + } + + public static function validateInt($value, $params) + { + return is_numeric($value) && (int) $value == $value; + } + + protected static function filterInt($value, $params) + { + return (int) $value; + } + + public static function validateArray($value, $params) + { + return is_array($value) || ($value instanceof \ArrayAccess + && $value instanceof \Traversable + && $value instanceof \Countable); + } + + public static function validateJson($value, $params) + { + return (bool) (json_decode($value)); + } +} diff --git a/system/src/Grav/Common/Debugger.php b/system/src/Grav/Common/Debugger.php new file mode 100644 index 0000000..219fa6a --- /dev/null +++ b/system/src/Grav/Common/Debugger.php @@ -0,0 +1,217 @@ +debugbar = new StandardDebugBar(); + $this->debugbar['time']->addMeasure('Loading', $this->debugbar['time']->getRequestStartTime(), microtime(true)); + } + + /** + * Initialize the debugger + * + * @return $this + * @throws \DebugBar\DebugBarException + */ + public function init() + { + $this->grav = Grav::instance(); + $this->config = $this->grav['config']; + + if ($this->enabled()) { + $this->debugbar->addCollector(new ConfigCollector((array)$this->config->get('system'), 'Config')); + $this->debugbar->addCollector(new ConfigCollector((array)$this->config->get('plugins'), 'Plugins')); + } + + return $this; + } + + /** + * Set/get the enabled state of the debugger + * + * @param bool $state If null, the method returns the enabled value. If set, the method sets the enabled state + * + * @return null + */ + public function enabled($state = null) + { + if (isset($state)) { + $this->enabled = $state; + } else { + if (!isset($this->enabled)) { + $this->enabled = $this->config->get('system.debugger.enabled'); + } + } + + return $this->enabled; + } + + /** + * Add the debugger assets to the Grav Assets + * + * @return $this + */ + public function addAssets() + { + if ($this->enabled()) { + /** @var Assets $assets */ + $assets = $this->grav['assets']; + + // Add jquery library + $assets->add('jquery', 101); + + $this->renderer = $this->debugbar->getJavascriptRenderer(); + $this->renderer->setIncludeVendors(false); + + // Get the required CSS files + list($css_files, $js_files) = $this->renderer->getAssets(null, JavascriptRenderer::RELATIVE_URL); + foreach ($css_files as $css) { + $assets->addCss($css); + } + + $assets->addCss('/system/assets/debugger.css'); + + foreach ($js_files as $js) { + $assets->addJs($js); + } + } + + return $this; + } + + /** + * Adds a data collector + * + * @param $collector + * + * @return $this + * @throws \DebugBar\DebugBarException + */ + public function addCollector($collector) + { + $this->debugbar->addCollector($collector); + + return $this; + } + + /** + * Returns a data collector + * + * @param $collector + * + * @return \DebugBar\DataCollector\DataCollectorInterface + * @throws \DebugBar\DebugBarException + */ + public function getCollector($collector) + { + return $this->debugbar->getCollector($collector); + } + + /** + * Displays the debug bar + * + * @return $this + */ + public function render() + { + if ($this->enabled()) { + echo $this->renderer->render(); + } + + return $this; + } + + /** + * Sends the data through the HTTP headers + * + * @return $this + */ + public function sendDataInHeaders() + { + $this->debugbar->sendDataInHeaders(); + + return $this; + } + + /** + * Start a timer with an associated name and description + * + * @param $name + * @param string|null $description + * + * @return $this + */ + public function startTimer($name, $description = null) + { + if ($name[0] == '_' || $this->config->get('system.debugger.enabled')) { + $this->debugbar['time']->startMeasure($name, $description); + $this->timers[] = $name; + } + + return $this; + } + + /** + * Stop the named timer + * + * @param string $name + * + * @return $this + */ + public function stopTimer($name) + { + if (in_array($name, $this->timers) && ($name[0] == '_' || $this->config->get('system.debugger.enabled'))) { + $this->debugbar['time']->stopMeasure($name); + } + + return $this; + } + + /** + * Dump variables into the Messages tab of the Debug Bar + * + * @param $message + * @param string $label + * @param bool $isString + * + * @return $this + */ + public function addMessage($message, $label = 'info', $isString = true) + { + if ($this->enabled()) { + $this->debugbar['messages']->addMessage($message, $label, $isString); + } + + return $this; + } +} diff --git a/system/src/Grav/Common/Errors/Errors.php b/system/src/Grav/Common/Errors/Errors.php new file mode 100644 index 0000000..6880684 --- /dev/null +++ b/system/src/Grav/Common/Errors/Errors.php @@ -0,0 +1,60 @@ +get('system.errors'); + $jsonRequest = $_SERVER && $_SERVER['HTTP_ACCEPT'] && $_SERVER['HTTP_ACCEPT'] == 'application/json'; + + // Setup Whoops-based error handler + $whoops = new \Whoops\Run; + + if (isset($config['display'])) { + if ($config['display']) { + $error_page = new Whoops\Handler\PrettyPageHandler; + $error_page->setPageTitle('Crikey! There was an error...'); + $error_page->addResourcePath(GRAV_ROOT . '/system/assets'); + $error_page->addCustomCss('whoops.css'); + $whoops->pushHandler($error_page); + } else { + $whoops->pushHandler(new SimplePageHandler); + } + } + + if (method_exists('Whoops\Util\Misc', 'isAjaxRequest')) { //Whoops 2.0 + if (Whoops\Util\Misc::isAjaxRequest() || $jsonRequest) { + $whoops->pushHandler(new Whoops\Handler\JsonResponseHandler); + } + } elseif (function_exists('Whoops\isAjaxRequest')) { //Whoops 2.0.0-alpha + if (Whoops\isAjaxRequest() || $jsonRequest) { + $whoops->pushHandler(new Whoops\Handler\JsonResponseHandler); + } + } else { //Whoops 1.x + $json_page = new Whoops\Handler\JsonResponseHandler; + $json_page->onlyForAjaxRequests(true); + } + + if (isset($config['log']) && $config['log']) { + $logger = $grav['log']; + $whoops->pushHandler(function($exception, $inspector, $run) use ($logger) { + try { + $logger->addCritical($exception->getMessage() . ' - Trace: ' . $exception->getTraceAsString()); + } catch (\Exception $e) { + echo $e; + } + }, 'log'); + } + + $whoops->register(); + } +} diff --git a/system/src/Grav/Common/Errors/Resources/error.css b/system/src/Grav/Common/Errors/Resources/error.css new file mode 100644 index 0000000..11ce3fd --- /dev/null +++ b/system/src/Grav/Common/Errors/Resources/error.css @@ -0,0 +1,52 @@ +html, body { + height: 100% +} +body { + margin:0 3rem; + padding:0; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 1.5rem; + line-height: 1.4; + display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */ + display: -moz-box; /* OLD - Firefox 19- (buggy but mostly works) */ + display: -ms-flexbox; /* TWEENER - IE 10 */ + display: -webkit-flex; /* NEW - Chrome */ + display: flex; + -webkit-align-items: center; + align-items: center; + -webkit-justify-content: center; + justify-content: center; +} +.container { + margin: 0rem; + max-width: 600px; + padding-bottom:5rem; +} + +header { + color: #000; + font-size: 4rem; + letter-spacing: 2px; + line-height: 1.1; + margin-bottom: 2rem; +} +p { + font-family: Optima, Segoe, "Segoe UI", Candara, Calibri, Arial, sans-serif; + color: #666; +} + +h5 { + font-weight: normal; + color: #999; + font-size: 1rem; +} + +h6 { + font-weight: normal; + color: #999; +} + +code { + font-weight: bold; + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; +} diff --git a/system/src/Grav/Common/Errors/Resources/layout.html.php b/system/src/Grav/Common/Errors/Resources/layout.html.php new file mode 100644 index 0000000..6699959 --- /dev/null +++ b/system/src/Grav/Common/Errors/Resources/layout.html.php @@ -0,0 +1,30 @@ + + + + + + Whoops there was an error! + + + +
+
+
+ Server Error +
+ + + +

Sorry, something went terribly wrong!

+ +

-

+ +
For further details please review your logs/ folder, or enable displaying of errors in your system configuration.
+
+
+ + diff --git a/system/src/Grav/Common/Errors/SimplePageHandler.php b/system/src/Grav/Common/Errors/SimplePageHandler.php new file mode 100644 index 0000000..16d0a88 --- /dev/null +++ b/system/src/Grav/Common/Errors/SimplePageHandler.php @@ -0,0 +1,96 @@ +searchPaths[] = __DIR__ . "/Resources"; + } + + /** + * @return int|null + */ + public function handle() + { + $inspector = $this->getInspector(); + + $helper = new TemplateHelper(); + $templateFile = $this->getResource("layout.html.php"); + $cssFile = $this->getResource("error.css"); + + $code = $inspector->getException()->getCode(); + $message = $inspector->getException()->getMessage(); + + if ($inspector->getException() instanceof \ErrorException) { + $code = Misc::translateErrorCode($code); + } + + $vars = array( + "stylesheet" => file_get_contents($cssFile), + "code" => $code, + "message" => $message, + ); + + $helper->setVariables($vars); + $helper->render($templateFile); + + return Handler::QUIT; + } + + /** + * @param $resource + * + * @return string + */ + protected function getResource($resource) + { + // If the resource was found before, we can speed things up + // by caching its absolute, resolved path: + if (isset($this->resourceCache[$resource])) { + return $this->resourceCache[$resource]; + } + + // Search through available search paths, until we find the + // resource we're after: + foreach ($this->searchPaths as $path) { + $fullPath = $path . "/$resource"; + + if (is_file($fullPath)) { + // Cache the result: + $this->resourceCache[$resource] = $fullPath; + return $fullPath; + } + } + + // If we got this far, nothing was found. + throw new \RuntimeException( + "Could not find resource '$resource' in any resource paths." + . "(searched: " . join(", ", $this->searchPaths). ")" + ); + } + + public function addResourcePath($path) + { + if (!is_dir($path)) { + throw new \InvalidArgumentException( + "'$path' is not a valid directory" + ); + } + + array_unshift($this->searchPaths, $path); + } + + public function getResourcePaths() + { + return $this->searchPaths; + } +} diff --git a/system/src/Grav/Common/File/CompiledFile.php b/system/src/Grav/Common/File/CompiledFile.php new file mode 100644 index 0000000..6b6b769 --- /dev/null +++ b/system/src/Grav/Common/File/CompiledFile.php @@ -0,0 +1,78 @@ +settings(['native' => true, 'compat' => true]); + + // If nothing has been loaded, attempt to get pre-compiled version of the file first. + if ($var === null && $this->raw === null && $this->content === null) { + $key = md5($this->filename); + $file = PhpFile::instance(CACHE_DIR . "compiled/files/{$key}{$this->extension}.php"); + $modified = $this->modified(); + + if (!$modified) { + return $this->decode($this->raw()); + } + + $class = get_class($this); + + $cache = $file->exists() ? $file->content() : null; + + // Load real file if cache isn't up to date (or is invalid). + if ( + !isset($cache['@class']) + || $cache['@class'] != $class + || $cache['modified'] != $modified + || $cache['filename'] != $this->filename + ) { + // Attempt to lock the file for writing. + try { + $file->lock(false); + } catch (\Exception $e) { + // Another process has locked the file; we will check this in a bit. + } + + // Decode RAW file into compiled array. + $data = (array) $this->decode($this->raw()); + $cache = [ + '@class' => $class, + 'filename' => $this->filename, + 'modified' => $modified, + 'data' => $data + ]; + + // If compiled file wasn't already locked by another process, save it. + if ($file->locked() !== false) { + $file->save($cache); + $file->unlock(); + } + } + $file->free(); + + $this->content = $cache['data']; + } + + return parent::content($var); + } +} diff --git a/system/src/Grav/Common/File/CompiledMarkdownFile.php b/system/src/Grav/Common/File/CompiledMarkdownFile.php new file mode 100644 index 0000000..669a4eb --- /dev/null +++ b/system/src/Grav/Common/File/CompiledMarkdownFile.php @@ -0,0 +1,9 @@ +getMTime(); + if ($dir_modified > $last_modified) { + $last_modified = $dir_modified; + } + } + + return $last_modified; + } + + /** + * Recursively find the last modified time under given path by file. + * + * @param string $path + * @param string $extensions which files to search for specifically + * + * @return int + */ + public static function lastModifiedFile($path, $extensions = 'md|yaml') + { + $last_modified = 0; + + $directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS); + $recursive = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST); + $iterator = new \RegexIterator($recursive, '/^.+\.'.$extensions.'$/i'); + + /** @var \RecursiveDirectoryIterator $file */ + foreach ($iterator as $filepath => $file) { + $file_modified = $file->getMTime(); + if ($file_modified > $last_modified) { + $last_modified = $file_modified; + } + } + + return $last_modified; + } + + /** + * Get relative path between target and base path. If path isn't relative, return full path. + * + * @param string $path + * @param mixed|string $base + * + * @return string + */ + public static function getRelativePath($path, $base = GRAV_ROOT) + { + if ($base) { + $base = preg_replace('![\\\/]+!', '/', $base); + $path = preg_replace('![\\\/]+!', '/', $path); + if (strpos($path, $base) === 0) { + $path = ltrim(substr($path, strlen($base)), '/'); + } + } + + return $path; + } + + /** + * Get relative path between target and base path. If path isn't relative, return full path. + * + * @param string $path + * @param string $base + * @return string + */ + public static function getRelativePathDotDot($path, $base) + { + $base = preg_replace('![\\\/]+!', '/', $base); + $path = preg_replace('![\\\/]+!', '/', $path); + + if ($path === $base) { + return ''; + } + + $baseParts = explode('/', isset($base[0]) && '/' === $base[0] ? substr($base, 1) : $base); + $pathParts = explode('/', isset($path[0]) && '/' === $path[0] ? substr($path, 1) : $path); + + array_pop($baseParts); + $lastPart = array_pop($pathParts); + foreach ($baseParts as $i => $directory) { + if (isset($pathParts[$i]) && $pathParts[$i] === $directory) { + unset($baseParts[$i], $pathParts[$i]); + } else { + break; + } + } + $pathParts[] = $lastPart; + $path = str_repeat('../', count($baseParts)) . implode('/', $pathParts); + + return '' === $path + || '/' === $path[0] + || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos) + ? "./$path" : $path; + } + + /** + * Shift first directory out of the path. + * + * @param string $path + * @return string + */ + public static function shift(&$path) + { + $parts = explode('/', trim($path, '/'), 2); + $result = array_shift($parts); + $path = array_shift($parts); + + return $result ?: null; + } + + /** + * Return recursive list of all files and directories under given path. + * + * @param string $path + * @param array $params + * @return array + * @throws \RuntimeException + */ + public static function all($path, array $params = []) + { + if ($path === false) { + throw new \RuntimeException("Path to {$path} doesn't exist."); + } + + $compare = isset($params['compare']) ? 'get' . $params['compare'] : null; + $pattern = isset($params['pattern']) ? $params['pattern'] : null; + $filters = isset($params['filters']) ? $params['filters'] : null; + $recursive = isset($params['recursive']) ? $params['recursive'] : true; + $levels = isset($params['levels']) ? $params['levels'] : -1; + $key = isset($params['key']) ? 'get' . $params['key'] : null; + $value = isset($params['value']) ? 'get' . $params['value'] : ($recursive ? 'getSubPathname' : 'getFilename'); + $folders = isset($params['folders']) ? $params['folders'] : true; + $files = isset($params['files']) ? $params['files'] : true; + + if ($recursive) { + $directory = new \RecursiveDirectoryIterator($path, + \RecursiveDirectoryIterator::SKIP_DOTS + \FilesystemIterator::UNIX_PATHS + \FilesystemIterator::CURRENT_AS_SELF); + $iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST); + $iterator->setMaxDepth(max($levels, -1)); + } else { + $iterator = new \FilesystemIterator($path); + } + + $results = []; + + /** @var \RecursiveDirectoryIterator $file */ + foreach ($iterator as $file) { + // Ignore hidden files. + if ($file->getFilename()[0] == '.') { + continue; + } + if (!$folders && $file->isDir()) { + continue; + } + if (!$files && $file->isFile()) { + continue; + } + if ($compare && $pattern && !preg_match($pattern, $file->{$compare}())) { + continue; + } + $fileKey = $key ? $file->{$key}() : null; + $filePath = $file->{$value}(); + if ($filters) { + if (isset($filters['key'])) { + $pre = !empty($filters['pre-key']) ? $filters['pre-key'] : ''; + $fileKey = $pre . preg_replace($filters['key'], '', $fileKey); + } + if (isset($filters['value'])) { + $filter = $filters['value']; + if (is_callable($filter)) { + $filePath = call_user_func($filter, $file); + } else { + $filePath = preg_replace($filter, '', $filePath); + } + } + } + + if ($fileKey !== null) { + $results[$fileKey] = $filePath; + } else { + $results[] = $filePath; + } + } + + return $results; + } + + /** + * Recursively copy directory in filesystem. + * + * @param string $source + * @param string $target + * @param string $ignore Ignore files matching pattern (regular expression). + * @throws \RuntimeException + */ + public static function copy($source, $target, $ignore = null) + { + $source = rtrim($source, '\\/'); + $target = rtrim($target, '\\/'); + + if (!is_dir($source)) { + throw new \RuntimeException('Cannot copy non-existing folder.'); + } + + // Make sure that path to the target exists before copying. + self::create($target); + + $success = true; + + // Go through all sub-directories and copy everything. + $files = self::all($source); + foreach ($files as $file) { + if ($ignore && preg_match($ignore, $file)) { + continue; + } + $src = $source .'/'. $file; + $dst = $target .'/'. $file; + + if (is_dir($src)) { + // Create current directory (if it doesn't exist). + if (!is_dir($dst)) { + $success &= @mkdir($dst, 0777, true); + } + } else { + // Or copy current file. + $success &= @copy($src, $dst); + } + } + + if (!$success) { + $error = error_get_last(); + throw new \RuntimeException($error['message']); + } + + // Make sure that the change will be detected when caching. + @touch(dirname($target)); + } + + /** + * Move directory in filesystem. + * + * @param string $source + * @param string $target + * @throws \RuntimeException + */ + public static function move($source, $target) + { + if (!is_dir($source)) { + 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)); + + // Just rename the directory. + $success = @rename($source, $target); + + if (!$success) { + $error = error_get_last(); + throw new \RuntimeException($error['message']); + } + + // Make sure that the change will be detected when caching. + @touch(dirname($source)); + @touch(dirname($target)); + } + + /** + * Recursively delete directory from filesystem. + * + * @param string $target + * @param bool $include_target + * @return bool + */ + public static function delete($target, $include_target = true) + { + if (!is_dir($target)) { + return false; + } + + $success = self::doDelete($target, $include_target); + + if (!$success) { + $error = error_get_last(); + throw new \RuntimeException($error['message']); + } + + // Make sure that the change will be detected when caching. + if ($include_target) { + @touch(dirname($target)); + } else { + @touch($target); + } + + return $success; + } + + /** + * @param string $folder + * @throws \RuntimeException + * @internal + */ + public static function mkdir($folder) + { + self::create($folder); + } + + /** + * @param string $folder + * @throws \RuntimeException + * @internal + */ + public static function create($folder) + { + if (is_dir($folder)) { + return; + } + + $success = @mkdir($folder, 0777, true); + + if (!$success) { + $error = error_get_last(); + throw new \RuntimeException($error['message']); + } + } + + /** + * Recursive copy of one directory to another + * + * @param $src + * @param $dest + * + * @return bool + */ + public static function rcopy($src, $dest) + { + + // If the src is not a directory do a simple file copy + if (!is_dir($src)) { + copy($src, $dest); + return true; + } + + // If the destination directory does not exist create it + if (!is_dir($dest)) { + if (!mkdir($dest)) { + // If the destination directory could not be created stop processing + return false; + } + } + + // Open the source directory to read in files + $i = new \DirectoryIterator($src); + /** @var \DirectoryIterator $f */ + foreach ($i as $f) { + if ($f->isFile()) { + copy($f->getRealPath(), "$dest/" . $f->getFilename()); + } else { + if (!$f->isDot() && $f->isDir()) { + static::rcopy($f->getRealPath(), "$dest/$f"); + } + } + } + return true; + } + + /** + * @param string $folder + * @param bool $include_target + * @return bool + * @internal + */ + protected static function doDelete($folder, $include_target = true) + { + // Special case for symbolic links. + if (is_link($folder)) { + return @unlink($folder); + } + + $files = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($folder, \RecursiveDirectoryIterator::SKIP_DOTS), + \RecursiveIteratorIterator::CHILD_FIRST + ); + + /** @var \DirectoryIterator $fileinfo */ + foreach ($files as $fileinfo) { + if ($fileinfo->isDir()) { + if (false === @rmdir($fileinfo->getRealPath())) { + return false; + } + } else { + if (false === @unlink($fileinfo->getRealPath())) { + return false; + } + } + } + + return $include_target ? @rmdir($folder) : true; + } +} diff --git a/system/src/Grav/Common/Filesystem/RecursiveFolderFilterIterator.php b/system/src/Grav/Common/Filesystem/RecursiveFolderFilterIterator.php new file mode 100644 index 0000000..a84a519 --- /dev/null +++ b/system/src/Grav/Common/Filesystem/RecursiveFolderFilterIterator.php @@ -0,0 +1,44 @@ +get('system.pages.ignore_folders'); + } + } + + /** + * Check whether the current element of the iterator is acceptable + * + * @return bool true if the current element is acceptable, otherwise false. + */ + public function accept() + { + /** @var $current \SplFileInfo */ + $current = $this->current(); + + if ($current->isDir() && !in_array($current->getFilename(), $this::$folder_ignores)) { + return true; + } + return false; + } +} diff --git a/system/src/Grav/Common/GPM/AbstractCollection.php b/system/src/Grav/Common/GPM/AbstractCollection.php new file mode 100644 index 0000000..7c1974d --- /dev/null +++ b/system/src/Grav/Common/GPM/AbstractCollection.php @@ -0,0 +1,32 @@ +items as $name => $package) { + $items[$name] = $package->toArray(); + } + + return json_encode($items); + } + + public function toArray() + { + $items = []; + + foreach ($this->items as $name => $package) { + $items[$name] = $package->toArray(); + } + + return $items; + } +} diff --git a/system/src/Grav/Common/GPM/Common/AbstractPackageCollection.php b/system/src/Grav/Common/GPM/Common/AbstractPackageCollection.php new file mode 100644 index 0000000..46c9e89 --- /dev/null +++ b/system/src/Grav/Common/GPM/Common/AbstractPackageCollection.php @@ -0,0 +1,34 @@ +items as $name => $package) { + $items[$name] = $package->toArray(); + } + + return json_encode($items); + } + + public function toArray() + { + $items = []; + + foreach ($this->items as $name => $package) { + $items[$name] = $package->toArray(); + } + + return $items; + } +} diff --git a/system/src/Grav/Common/GPM/Common/CachedCollection.php b/system/src/Grav/Common/GPM/Common/CachedCollection.php new file mode 100644 index 0000000..fd6ae94 --- /dev/null +++ b/system/src/Grav/Common/GPM/Common/CachedCollection.php @@ -0,0 +1,21 @@ + $item) { + $this->append([$name => $item]); + } + } +} diff --git a/system/src/Grav/Common/GPM/Common/Package.php b/system/src/Grav/Common/GPM/Common/Package.php new file mode 100644 index 0000000..ff9a087 --- /dev/null +++ b/system/src/Grav/Common/GPM/Common/Package.php @@ -0,0 +1,42 @@ +data = $package; + + if ($type) { + $this->data->set('package_type', $type); + } + } + + public function getData() { + return $this->data; + } + + public function __get($key) { + return $this->data->get($key); + } + + public function __isset($key) { + return isset($this->data->$key); + } + + public function __toString() { + return $this->toJson(); + } + + public function toJson() { + return $this->data->toJson(); + } + + public function toArray() { + return $this->data->toArray(); + } + +} diff --git a/system/src/Grav/Common/GPM/GPM.php b/system/src/Grav/Common/GPM/GPM.php new file mode 100644 index 0000000..3cdb64b --- /dev/null +++ b/system/src/Grav/Common/GPM/GPM.php @@ -0,0 +1,409 @@ + 'user/plugins/%name%', 'themes' => 'user/themes/%name%', 'skeletons' => 'user/']; + + /** + * Creates a new GPM instance with Local and Remote packages available + * @param boolean $refresh Applies to Remote Packages only and forces a refetch of data + * @param callable $callback Either a function or callback in array notation + */ + public function __construct($refresh = false, $callback = null) + { + $this->installed = new Local\Packages(); + try { + $this->repository = new Remote\Packages($refresh, $callback); + $this->grav = new Remote\Grav($refresh, $callback); + } catch (\Exception $e) { + } + } + + /** + * Returns the Locally installed packages + * @return Iterator The installed packages + */ + public function getInstalled() + { + return $this->installed; + } + + /** + * Returns the amount of locally installed packages + * @return integer Amount of installed packages + */ + public function countInstalled() + { + $installed = $this->getInstalled(); + + return count($installed['plugins']) + count($installed['themes']); + } + + /** + * Return the instance of a specific Plugin + * @param string $slug The slug of the Plugin + * @return Local\Package The instance of the Plugin + */ + public function getInstalledPlugin($slug) + { + return $this->installed['plugins'][$slug]; + } + + /** + * Returns the Locally installed plugins + * @return Iterator The installed plugins + */ + public function getInstalledPlugins() + { + return $this->installed['plugins']; + } + + /** + * Checks if a Plugin is installed + * @param string $slug The slug of the Plugin + * @return boolean True if the Plugin has been installed. False otherwise + */ + public function isPluginInstalled($slug) + { + return isset($this->installed['plugins'][$slug]); + } + + /** + * Return the instance of a specific Theme + * @param string $slug The slug of the Theme + * @return Local\Package The instance of the Theme + */ + public function getInstalledTheme($slug) + { + return $this->installed['themes'][$slug]; + } + + /** + * Returns the Locally installed themes + * @return Iterator The installed themes + */ + public function getInstalledThemes() + { + return $this->installed['themes']; + } + + /** + * Checks if a Theme is installed + * @param string $slug The slug of the Theme + * @return boolean True if the Theme has been installed. False otherwise + */ + public function isThemeInstalled($slug) + { + return isset($this->installed['themes'][$slug]); + } + + /** + * Returns the amount of updates available + * @return integer Amount of available updates + */ + public function countUpdates() + { + $count = 0; + + $count += count($this->getUpdatablePlugins()); + $count += count($this->getUpdatableThemes()); + + return $count; + } + + /** + * Returns an array of Plugins and Themes that can be updated. + * Plugins and Themes are extended with the `available` property that relies to the remote version + * @return array Array of updatable Plugins and Themes. + * Format: ['total' => int, 'plugins' => array, 'themes' => array] + */ + public function getUpdatable() + { + $plugins = $this->getUpdatablePlugins(); + $themes = $this->getUpdatableThemes(); + + $items = [ + 'total' => count($plugins)+count($themes), + 'plugins' => $plugins, + 'themes' => $themes + ]; + + return $items; + } + + /** + * Returns an array of Plugins that can be updated. + * The Plugins are extended with the `available` property that relies to the remote version + * @return Iterator Array of updatable Plugins + */ + public function getUpdatablePlugins() + { + $items = []; + $repository = $this->repository['plugins']; + + // local cache to speed things up + if (isset($this->cache[__METHOD__])) { + return $this->cache[__METHOD__]; + } + + foreach ($this->installed['plugins'] as $slug => $plugin) { + if (!isset($repository[$slug]) || $plugin->symlink) { + continue; + } + + $local_version = $plugin->version ? $plugin->version : 'Unknown'; + $remote_version = $repository[$slug]->version; + + if (version_compare($local_version, $remote_version) < 0) { + $repository[$slug]->available = $remote_version; + $repository[$slug]->version = $local_version; + $items[$slug] = $repository[$slug]; + } + } + + $this->cache[__METHOD__] = $items; + + return $items; + } + + /** + * Check if a Plugin or Theme is updatable + * @param string $slug The slug of the package + * @return boolean True if updatable. False otherwise or if not found + */ + public function isUpdatable($slug) + { + return $this->isPluginUpdatable($slug) || $this->isThemeUpdatable($slug); + } + + /** + * Checks if a Plugin is updatable + * @param string $plugin The slug of the Plugin + * @return boolean True if the Plugin is updatable. False otherwise + */ + public function isPluginUpdatable($plugin) + { + return array_key_exists($plugin, (array) $this->getUpdatablePlugins()); + } + + /** + * Returns an array of Themes that can be updated. + * The Themes are extended with the `available` property that relies to the remote version + * @return Iterator Array of updatable Themes + */ + public function getUpdatableThemes() + { + $items = []; + $repository = $this->repository['themes']; + + // local cache to speed things up + if (isset($this->cache[__METHOD__])) { + return $this->cache[__METHOD__]; + } + + foreach ($this->installed['themes'] as $slug => $plugin) { + if (!isset($repository[$slug]) || $plugin->symlink) { + continue; + } + + $local_version = $plugin->version ? $plugin->version : 'Unknown'; + $remote_version = $repository[$slug]->version; + + if (version_compare($local_version, $remote_version) < 0) { + $repository[$slug]->available = $remote_version; + $repository[$slug]->version = $local_version; + $items[$slug] = $repository[$slug]; + } + } + + $this->cache[__METHOD__] = $items; + + return $items; + } + + /** + * Checks if a Theme is Updatable + * @param string $theme The slug of the Theme + * @return boolean True if the Theme is updatable. False otherwise + */ + public function isThemeUpdatable($theme) + { + return array_key_exists($theme, (array) $this->getUpdatableThemes()); + } + + /** + * Returns a Plugin from the repository + * @param string $slug The slug of the Plugin + * @return mixed Package if found, NULL if not + */ + public function getRepositoryPlugin($slug) + { + return @$this->repository['plugins'][$slug]; + } + + /** + * Returns the list of Plugins available in the repository + * @return Iterator The Plugins remotely available + */ + public function getRepositoryPlugins() + { + return $this->repository['plugins']; + } + + /** + * Returns a Theme from the repository + * @param string $slug The slug of the Theme + * @return mixed Package if found, NULL if not + */ + public function getRepositoryTheme($slug) + { + return @$this->repository['themes'][$slug]; + } + + /** + * Returns the list of Themes available in the repository + * @return Iterator The Themes remotely available + */ + public function getRepositoryThemes() + { + return $this->repository['themes']; + } + + /** + * Returns the list of Plugins and Themes available in the repository + * @return array Array of available Plugins and Themes + * Format: ['plugins' => array, 'themes' => array] + */ + public function getRepository() + { + return $this->repository; + } + + /** + * Searches for a Package in the repository + * @param string $search Can be either the slug or the name + * @return Remote\Package Package if found, FALSE if not + */ + public function findPackage($search) + { + $search = strtolower($search); + if ($found = $this->getRepositoryTheme($search)) { + return $found; + } + + if ($found = $this->getRepositoryPlugin($search)) { + return $found; + } + + $themes = $this->getRepositoryThemes(); + $plugins = $this->getRepositoryPlugins(); + + if (!$themes && !$plugins) { + if (!is_writable(ROOT_DIR . '/cache/gpm')) { + throw new \RuntimeException("The cache/gpm folder is not writable. Please check the folder permissions."); + } + + throw new \RuntimeException("GPM not reachable. Please check your internet connection or check the Grav site is reachable"); + } + + if ($themes) foreach ($themes as $slug => $theme) { + if ($search == $slug || $search == $theme->name) { + return $theme; + } + } + + if ($plugins) foreach ($plugins as $slug => $plugin) { + if ($search == $slug || $search == $plugin->name) { + return $plugin; + } + } + + return false; + } + + /** + * Returns the list of Plugins and Themes available in the repository + * @return array Array of available Plugins and Themes + * Format: ['plugins' => array, 'themes' => array] + */ + /** + * Searches for a list of Packages in the repository + * @param array $searches An array of either slugs or names + * @return array Array of found Packages + * Format: ['total' => int, 'not_found' => array, ] + */ + public function findPackages($searches = []) + { + $packages = ['total' => 0, 'not_found' => []]; + $inflector = new Inflector(); + + foreach ($searches as $search) { + $repository = ''; + // if this is an object, get the search data from the key + if (is_object($search)) { + $search = (array) $search; + $key = key($search); + $repository = $search[$key]; + $search = $key; + } + + if ($found = $this->findPackage($search)) { + // set override repository if provided + if ($repository) { + $found->override_repository = $repository; + } + if (!isset($packages[$found->package_type])) { + $packages[$found->package_type] = []; + } + + $packages[$found->package_type][$found->slug] = $found; + $packages['total']++; + } else { + // make a best guess at the type based on the repo URL + if (Utils::contains($repository, '-theme')) { + $type = 'themes'; + } else { + $type = 'plugins'; + } + + $not_found = new \stdClass(); + $not_found->name = $inflector->camelize($search); + $not_found->slug = $search; + $not_found->package_type = $type; + $not_found->install_path = str_replace('%name%', $search, $this->install_paths[$type]); + $not_found->override_repository = $repository; + $packages['not_found'][$search] = $not_found; + } + } + + return $packages; + } +} diff --git a/system/src/Grav/Common/GPM/Installer.php b/system/src/Grav/Common/GPM/Installer.php new file mode 100644 index 0000000..c4b3f73 --- /dev/null +++ b/system/src/Grav/Common/GPM/Installer.php @@ -0,0 +1,370 @@ + true, + 'ignore_symlinks' => true, + 'sophisticated' => false, + 'theme' => false, + 'install_path' => '', + 'exclude_checks' => [self::EXISTS, self::NOT_FOUND, self::IS_LINK] + ]; + + /** + * Installs a given package to a given destination. + * + * @param string $package The local path to the ZIP package + * @param string $destination The local path to the Grav Instance + * @param array $options Options to use for installing. ie, ['install_path' => 'user/themes/antimatter'] + * + * @return boolean True if everything went fine, False otherwise. + */ + public static function install($package, $destination, $options = []) + { + $destination = rtrim($destination, DS); + $options = array_merge(self::$options, $options); + $install_path = rtrim($destination . DS . ltrim($options['install_path'], DS), DS); + + if (!self::isGravInstance($destination) || !self::isValidDestination($install_path, $options['exclude_checks'])) { + return false; + } + + if (self::lastErrorCode() == self::IS_LINK && $options['ignore_symlinks'] || + self::lastErrorCode() == self::EXISTS && !$options['overwrite']) { + return false; + } + + // Pre install checks + static::flightProcessing('pre_install', $install_path); + + $zip = new \ZipArchive(); + $archive = $zip->open($package); + $tmp = CACHE_DIR . 'tmp/Grav-' . uniqid(); + + if ($archive !== true) { + self::$error = self::ZIP_OPEN_ERROR; + + return false; + } + + Folder::mkdir($tmp); + + $unzip = $zip->extractTo($tmp); + + if (!$unzip) { + self::$error = self::ZIP_EXTRACT_ERROR; + + $zip->close(); + Folder::delete($tmp); + + return false; + } + + + if (!$options['sophisticated']) { + if ($options['theme']) { + self::copyInstall($zip, $install_path, $tmp); + } else { + self::moveInstall($zip, $install_path, $tmp); + } + } else { + self::sophisticatedInstall($zip, $install_path, $tmp); + } + + Folder::delete($tmp); + $zip->close(); + + // Post install checks + static::flightProcessing('post_install', $install_path); + + self::$error = self::OK; + + return true; + + } + + /** + * @param $state + * @param $install_path + */ + protected static function flightProcessing($state, $install_path) + { + $blueprints_path = $install_path . DS . 'blueprints.yaml'; + + if (file_exists($blueprints_path)) { + $package_yaml = Yaml::parse(file_get_contents($blueprints_path)); + if (isset($package_yaml['install'][$state]['create'])) { + foreach ((array) $package_yaml['install']['pre_install']['create'] as $file) { + Folder::mkdir($install_path . '/' . ltrim($file, '/')); + } + } + if (isset($package_yaml['install'][$state]['remove'])) { + foreach ((array) $package_yaml['install']['pre_install']['remove'] as $file) { + Folder::delete($install_path . '/' . ltrim($file, '/')); + } + } + } + } + + /** + * @param \ZipArchive $zip + * @param $install_path + * @param $tmp + * + * @return bool + */ + public static function moveInstall(\ZipArchive $zip, $install_path, $tmp) + { + $container = $zip->getNameIndex(0); + if (file_exists($install_path)) { + Folder::delete($install_path); + } + + Folder::move($tmp . DS . $container, $install_path); + + return true; + } + + /** + * @param \ZipArchive $zip + * @param $install_path + * @param $tmp + * + * @return bool + */ + public static function copyInstall(\ZipArchive $zip, $install_path, $tmp) + { + $firstDir = $zip->getNameIndex(0); + if (empty($firstDir)) { + throw new \RuntimeException("Directory $firstDir is missing"); + } else { + $tmp = realpath($tmp . DS . $firstDir); + Folder::rcopy($tmp, $install_path); + } + + return true; + } + + /** + * @param \ZipArchive $zip + * @param $install_path + * @param $tmp + * + * @return bool + */ + public static function sophisticatedInstall(\ZipArchive $zip, $install_path, $tmp) + { + for ($i = 0, $l = $zip->numFiles; $i < $l; $i++) { + $filename = $zip->getNameIndex($i); + $fileinfo = pathinfo($filename); + $depth = count(explode(DS, rtrim($filename, '/'))); + + if ($depth > 2) { + continue; + } + + $path = $install_path . DS . $fileinfo['basename']; + + if (is_link($path)) { + continue; + } else { + if (is_dir($path)) { + Folder::delete($path); + Folder::move($tmp . DS . $filename, $path); + + if ($fileinfo['basename'] == 'bin') { + foreach (glob($path . DS . '*') as $file) { + @chmod($file, 0755); + } + } + } else { + @unlink($path); + @copy($tmp . DS . $filename, $path); + } + } + } + + return true; + } + + /** + * Uninstalls one or more given package + * + * @param string $path The slug of the package(s) + * @param array $options Options to use for uninstalling + * + * @return boolean True if everything went fine, False otherwise. + */ + public static function uninstall($path, $options = []) + { + $options = array_merge(self::$options, $options); + if (!self::isValidDestination($path, $options['exclude_checks']) + ) { + return false; + } + + return Folder::delete($path); + } + + /** + * Runs a set of checks on the destination and sets the Error if any + * + * @param string $destination The directory to run validations at + * @param array $exclude An array of constants to exclude from the validation + * + * @return boolean True if validation passed. False otherwise + */ + public static function isValidDestination($destination, $exclude = []) + { + self::$error = 0; + self::$target = $destination; + + if (is_link($destination)) { + self::$error = self::IS_LINK; + } elseif (file_exists($destination)) { + self::$error = self::EXISTS; + } elseif (!file_exists($destination)) { + self::$error = self::NOT_FOUND; + } elseif (!is_dir($destination)) { + self::$error = self::NOT_DIRECTORY; + } + + if (count($exclude) && in_array(self::$error, $exclude)) { + return true; + } + + return !(self::$error); + } + + /** + * Validates if the given path is a Grav Instance + * + * @param string $target The local path to the Grav Instance + * + * @return boolean True if is a Grav Instance. False otherwise + */ + public static function isGravInstance($target) + { + self::$error = 0; + self::$target = $target; + + if ( + !file_exists($target . DS . 'index.php') || + !file_exists($target . DS . 'bin') || + !file_exists($target . DS . 'user') || + !file_exists($target . DS . 'system' . DS . 'config' . DS . 'system.yaml') + ) { + self::$error = self::NOT_GRAV_ROOT; + } + + return !self::$error; + } + + /** + * Returns the last error occurred in a string message format + * @return string The message of the last error + */ + public static function lastErrorMsg() + { + if (is_string(self::$error)) { + return self::$error; + } + + switch (self::$error) { + case 0: + $msg = 'No Error'; + break; + + case self::EXISTS: + $msg = 'The target path "' . self::$target . '" already exists'; + break; + + case self::IS_LINK: + $msg = 'The target path "' . self::$target . '" is a symbolic link'; + break; + + case self::NOT_FOUND: + $msg = 'The target path "' . self::$target . '" does not appear to exist'; + break; + + case self::NOT_DIRECTORY: + $msg = 'The target path "' . self::$target . '" does not appear to be a folder'; + break; + + case self::NOT_GRAV_ROOT: + $msg = 'The target path "' . self::$target . '" does not appear to be a Grav instance'; + break; + + case self::ZIP_OPEN_ERROR: + $msg = 'Unable to open the package file'; + break; + + case self::ZIP_EXTRACT_ERROR: + $msg = 'An error occurred while extracting the package'; + break; + + default: + $msg = 'Unknown Error'; + break; + } + + return $msg; + } + + /** + * Returns the last error code of the occurred error + * @return integer The code of the last error + */ + public static function lastErrorCode() + { + return self::$error; + } + + /** + * Allows to manually set an error + * @param int|string $error the Error code + */ + + public static function setError($error) + { + self::$error = $error; + } +} diff --git a/system/src/Grav/Common/GPM/Local/AbstractPackageCollection.php b/system/src/Grav/Common/GPM/Local/AbstractPackageCollection.php new file mode 100644 index 0000000..617af8b --- /dev/null +++ b/system/src/Grav/Common/GPM/Local/AbstractPackageCollection.php @@ -0,0 +1,16 @@ + $data) { + $data->set('slug', $name); + $this->items[$name] = new Package($data, $this->type); + } + } +} diff --git a/system/src/Grav/Common/GPM/Local/Package.php b/system/src/Grav/Common/GPM/Local/Package.php new file mode 100644 index 0000000..6505a59 --- /dev/null +++ b/system/src/Grav/Common/GPM/Local/Package.php @@ -0,0 +1,32 @@ +blueprints()->toArray()); + parent::__construct($data, $package_type); + + $this->settings = $package->toArray(); + + $html_description = \Parsedown::instance()->line($this->description); + $this->data->set('slug', $package->slug); + $this->data->set('description_html', $html_description); + $this->data->set('description_plain', strip_tags($html_description)); + $this->data->set('symlink', is_link(USER_DIR . $package_type . DS . $this->name)); + } + + /** + * @return mixed + */ + public function isEnabled() + { + return $this->settings['enabled']; + } +} diff --git a/system/src/Grav/Common/GPM/Local/Packages.php b/system/src/Grav/Common/GPM/Local/Packages.php new file mode 100644 index 0000000..a8ed6a4 --- /dev/null +++ b/system/src/Grav/Common/GPM/Local/Packages.php @@ -0,0 +1,17 @@ + new Plugins(), + 'themes' => new Themes() + ]; + + parent::__construct($items); + } +} diff --git a/system/src/Grav/Common/GPM/Local/Plugins.php b/system/src/Grav/Common/GPM/Local/Plugins.php new file mode 100644 index 0000000..b1c3c64 --- /dev/null +++ b/system/src/Grav/Common/GPM/Local/Plugins.php @@ -0,0 +1,22 @@ +all()); + } +} diff --git a/system/src/Grav/Common/GPM/Local/Themes.php b/system/src/Grav/Common/GPM/Local/Themes.php new file mode 100644 index 0000000..64d23b9 --- /dev/null +++ b/system/src/Grav/Common/GPM/Local/Themes.php @@ -0,0 +1,22 @@ +all()); + } +} diff --git a/system/src/Grav/Common/GPM/Remote/AbstractPackageCollection.php b/system/src/Grav/Common/GPM/Remote/AbstractPackageCollection.php new file mode 100644 index 0000000..0f113b4 --- /dev/null +++ b/system/src/Grav/Common/GPM/Remote/AbstractPackageCollection.php @@ -0,0 +1,54 @@ +findResource('cache://gpm', true, true); + $this->cache = new FilesystemCache($cache_dir); + + $this->repository = $repository; + $this->raw = $this->cache->fetch(md5($this->repository)); + + $this->fetch($refresh, $callback); + foreach (json_decode($this->raw, true) as $slug => $data) { + $this->items[$slug] = new Package($data, $this->type); + } + } + + public function fetch($refresh = false, $callback = null) + { + if (!$this->raw || $refresh) { + $response = Response::get($this->repository, [], $callback); + $this->raw = $response; + $this->cache->save(md5($this->repository), $this->raw, $this->lifetime); + } + + return $this->raw; + } +} diff --git a/system/src/Grav/Common/GPM/Remote/Grav.php b/system/src/Grav/Common/GPM/Remote/Grav.php new file mode 100644 index 0000000..cbc3179 --- /dev/null +++ b/system/src/Grav/Common/GPM/Remote/Grav.php @@ -0,0 +1,95 @@ +findResource('cache://gpm', true, true); + $this->cache = new FilesystemCache($cache_dir); + $this->raw = $this->cache->fetch(md5($this->repository)); + + $this->fetch($refresh, $callback); + + $this->data = json_decode($this->raw, true); + $this->version = isset($this->data['version']) ? $this->data['version'] : '-'; + $this->date = isset($this->data['date']) ? $this->data['date'] : '-'; + + if (isset($this->data['assets'])) foreach ($this->data['assets'] as $slug => $data) { + $this->items[$slug] = new Package($data); + } + } + + /** + * Returns the list of assets associated to the latest version of Grav + * @return array list of assets + */ + public function getAssets() + { + return $this->data['assets']; + } + + /** + * Returns the changelog list for each version of Grav + * @param string $diff the version number to start the diff from + * + * @return array changelog list for each version + */ + public function getChangelog($diff = null) + { + if (!$diff) { + return $this->data['changelog']; + } + + $diffLog = []; + foreach ($this->data['changelog'] as $version => $changelog) { + preg_match("/[\w-\.]+/", $version, $cleanVersion); + + if (!$cleanVersion || version_compare($diff, $cleanVersion[0], ">=")) { continue; } + + $diffLog[$version] = $changelog; + } + + return $diffLog; + } + + /** + * Returns the latest version of Grav available remotely + * @return string + */ + public function getVersion() + { + return $this->version; + } + + /** + * Return the release date of the latest Grav + * @return string + */ + public function getDate() + { + return $this->date; + } + + public function isUpdatable() + { + return version_compare(GRAV_VERSION, $this->getVersion(), '<'); + } + + public function isSymlink() + { + return is_link(GRAV_ROOT . DS . 'index.php'); + } +} diff --git a/system/src/Grav/Common/GPM/Remote/Package.php b/system/src/Grav/Common/GPM/Remote/Package.php new file mode 100644 index 0000000..9b0367f --- /dev/null +++ b/system/src/Grav/Common/GPM/Remote/Package.php @@ -0,0 +1,12 @@ + new Plugins($refresh, $callback), + 'themes' => new Themes($refresh, $callback) + ]; + + parent::__construct($items); + } +} diff --git a/system/src/Grav/Common/GPM/Remote/Plugins.php b/system/src/Grav/Common/GPM/Remote/Plugins.php new file mode 100644 index 0000000..036fc0c --- /dev/null +++ b/system/src/Grav/Common/GPM/Remote/Plugins.php @@ -0,0 +1,26 @@ +repository, $refresh, $callback); + } +} diff --git a/system/src/Grav/Common/GPM/Remote/Themes.php b/system/src/Grav/Common/GPM/Remote/Themes.php new file mode 100644 index 0000000..43a5af5 --- /dev/null +++ b/system/src/Grav/Common/GPM/Remote/Themes.php @@ -0,0 +1,26 @@ +repository, $refresh, $callback); + } +} diff --git a/system/src/Grav/Common/GPM/Response.php b/system/src/Grav/Common/GPM/Response.php new file mode 100644 index 0000000..0e57ec0 --- /dev/null +++ b/system/src/Grav/Common/GPM/Response.php @@ -0,0 +1,329 @@ + [ + CURLOPT_REFERER => 'Grav GPM', + CURLOPT_USERAGENT => 'Grav GPM', + CURLOPT_RETURNTRANSFER => true, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_TIMEOUT => 15, + CURLOPT_HEADER => false, + /** + * Example of callback parameters from within your own class + */ + //CURLOPT_NOPROGRESS => false, + //CURLOPT_PROGRESSFUNCTION => [$this, 'progress'] + ], + 'fopen' => [ + 'method' => 'GET', + 'user_agent' => 'Grav GPM', + 'max_redirects' => 5, + 'follow_location' => 1, + 'timeout' => 15, + /** + * Example of callback parameters from within your own class + */ + //'notification' => [$this, 'progress'] + ] + ]; + + /** + * Sets the preferred method to use for making HTTP calls. + * + * @param string $method Default is `auto` + * + * @return Response + */ + public static function setMethod($method = 'auto') + { + if (!in_array($method, ['auto', 'curl', 'fopen'])) { + $method = 'auto'; + } + + self::$method = $method; + + return new self(); + } + + /** + * Makes a request to the URL by using the preferred method + * + * @param string $uri URL to call + * @param array $options An array of parameters for both `curl` and `fopen` + * @param callable $callback Either a function or callback in array notation + * + * @return string The response of the request + */ + public static function get($uri = '', $options = [], $callback = null) + { + if (!self::isCurlAvailable() && !self::isFopenAvailable()) { + throw new \RuntimeException('Could not start an HTTP request. `allow_url_open` is disabled and `cURL` is not available'); + } + + // check if this function is available, if so use it to stop any timeouts + try { + if (!Utils::isFunctionDisabled('set_time_limit') && !ini_get('safe_mode') && function_exists('set_time_limit')) { + set_time_limit(0); + } + } catch (\Exception $e) { + } + + $options = array_replace_recursive(self::$defaults, $options); + $method = 'get' . ucfirst(strtolower(self::$method)); + + self::$callback = $callback; + return static::$method($uri, $options, $callback); + } + + /** + * Checks if cURL is available + * + * @return boolean + */ + public static function isCurlAvailable() + { + return function_exists('curl_version'); + } + + /** + * Checks if the remote fopen request is enabled in PHP + * + * @return boolean + */ + public static function isFopenAvailable() + { + return preg_match('/1|yes|on|true/i', ini_get('allow_url_fopen')); + } + + /** + * Progress normalized for cURL and Fopen + * Accepts a vsariable length of arguments passed in by stream method + * + * @return array Normalized array with useful data. + * Format: ['code' => int|false, 'filesize' => bytes, 'transferred' => bytes, 'percent' => int] + */ + public static function progress() + { + static $filesize = null; + + $args = func_get_args(); + $isCurlResource = is_resource($args[0]) && get_resource_type($args[0]) == 'curl'; + + $notification_code = !$isCurlResource ? $args[0] : false; + $bytes_transferred = $isCurlResource ? $args[2] : $args[4]; + + if ($isCurlResource) { + $filesize = $args[1]; + } elseif ($notification_code == STREAM_NOTIFY_FILE_SIZE_IS) { + $filesize = $args[5]; + } + + if ($bytes_transferred > 0) { + if ($notification_code == STREAM_NOTIFY_PROGRESS | STREAM_NOTIFY_COMPLETED || $isCurlResource) { + + $progress = [ + 'code' => $notification_code, + 'filesize' => $filesize, + 'transferred' => $bytes_transferred, + 'percent' => $filesize <= 0 ? '-' : round(($bytes_transferred * 100) / $filesize, 1) + ]; + + if (self::$callback !== null) { + call_user_func_array(self::$callback, [$progress]); + } + } + } + } + + /** + * Automatically picks the preferred method + * + * @return string The response of the request + */ + private static function getAuto() + { + if (!ini_get('open_basedir') && self::isFopenAvailable()) { + return self::getFopen(func_get_args()); + } + + if (self::isCurlAvailable()) { + return self::getCurl(func_get_args()); + } + } + + /** + * Starts a HTTP request via fopen + * + * @return string The response of the request + */ + private static function getFopen() + { + if (count($args = func_get_args()) == 1) { + $args = $args[0]; + } + + $uri = $args[0]; + $options = $args[1]; + $callback = $args[2]; + + // if proxy set add that + $proxy_url = self::getGrav()['config']->get('system.proxy_url'); + if ($proxy_url) { + $options['fopen']['proxy'] = $proxy_url; + } + + if ($callback) { + $options['fopen']['notification'] = ['self', 'progress']; + } + + $stream = stream_context_create(['http' => $options['fopen']], $options['fopen']); + $content = @file_get_contents($uri, false, $stream); + + if ($content === false) { + throw new \RuntimeException("Error while trying to download '$uri'"); + } + + return $content; + } + + /** + * Starts a HTTP request via cURL + * + * @return string The response of the request + */ + private static function getCurl() + { + $args = func_get_args(); + $args = count($args) > 1 ? $args : array_shift($args); + + $uri = $args[0]; + $options = $args[1]; + $callback = $args[2]; + + $ch = curl_init($uri); + + $response = static::curlExecFollow($ch, $options, $callback); + $errno = curl_errno($ch); + + if ($errno) { + $error_message = curl_strerror($errno); + throw new \RuntimeException("cURL error ({$errno}):\n {$error_message}"); + } + + curl_close($ch); + + return $response; + } + + /** + * @param $ch + * @param $options + * @param $callback + * + * @return bool|mixed + */ + private static function curlExecFollow($ch, $options, $callback) + { + if ($callback) { + curl_setopt_array( + $ch, + [ + CURLOPT_NOPROGRESS => false, + CURLOPT_PROGRESSFUNCTION => ['self', 'progress'] + ] + ); + } + + // if proxy set add that + $proxy_url = self::getGrav()['config']->get('system.proxy_url'); + if ($proxy_url) { + $options['curl'][CURLOPT_PROXY] = $proxy_url; + } + + // no open_basedir set, we can proceed normally + if (!ini_get('open_basedir')) { + curl_setopt_array($ch, $options['curl']); + return curl_exec($ch); + } + + $max_redirects = isset($options['curl'][CURLOPT_MAXREDIRS]) ? $options['curl'][CURLOPT_MAXREDIRS] : 3; + $options['curl'][CURLOPT_FOLLOWLOCATION] = false; + + // open_basedir set but no redirects to follow, we can disable followlocation and proceed normally + curl_setopt_array($ch, $options['curl']); + if ($max_redirects <= 0) { + return curl_exec($ch); + } + + $uri = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); + $rch = curl_copy_handle($ch); + + curl_setopt($rch, CURLOPT_HEADER, true); + curl_setopt($rch, CURLOPT_NOBODY, true); + curl_setopt($rch, CURLOPT_FORBID_REUSE, false); + curl_setopt($rch, CURLOPT_RETURNTRANSFER, true); + + do { + curl_setopt($rch, CURLOPT_URL, $uri); + $header = curl_exec($rch); + + if (curl_errno($rch)) { + $code = 0; + } else { + $code = curl_getinfo($rch, CURLINFO_HTTP_CODE); + if ($code == 301 || $code == 302) { + preg_match('/Location:(.*?)\n/', $header, $matches); + $uri = trim(array_pop($matches)); + } else { + $code = 0; + } + } + } while ($code && --$max_redirects); + + curl_close($rch); + + if (!$max_redirects) { + if ($max_redirects === null) { + trigger_error('Too many redirects. When following redirects, libcurl hit the maximum amount.', E_USER_WARNING); + } + + return false; + } + + curl_setopt($ch, CURLOPT_URL, $uri); + + return curl_exec($ch); + } +} diff --git a/system/src/Grav/Common/GPM/Upgrader.php b/system/src/Grav/Common/GPM/Upgrader.php new file mode 100644 index 0000000..61c9ad5 --- /dev/null +++ b/system/src/Grav/Common/GPM/Upgrader.php @@ -0,0 +1,122 @@ +remote = new Remote\Grav($refresh, $callback); + } + + /** + * Returns the release date of the latest version of Grav + * + * @return string + */ + public function getReleaseDate() + { + return $this->remote->getDate(); + } + + /** + * Returns the version of the installed Grav + * + * @return string + */ + public function getLocalVersion() + { + return GRAV_VERSION; + } + + /** + * Returns the version of the remotely available Grav + * + * @return string + */ + public function getRemoteVersion() + { + return $this->remote->getVersion(); + } + + /** + * Returns an array of assets available to download remotely + * + * @return array + */ + public function getAssets() + { + return $this->remote->getAssets(); + } + + /** + * Returns the changelog list for each version of Grav + * + * @param string $diff the version number to start the diff from + * + * @return array return the changelog list for each version + */ + public function getChangelog($diff = null) + { + return $this->remote->getChangelog($diff); + } + + /** + * @return bool + */ + public function meetsRequirements() + { + if (version_compare(PHP_VERSION, GRAV_PHP_MIN, '<')) { + return false; + } + + return true; + } + + /** + * Checks if the currently installed Grav is upgradable to a newer version + * + * @return boolean True if it's upgradable, False otherwise. + */ + public function isUpgradable() + { + return version_compare($this->getLocalVersion(), $this->getRemoteVersion(), "<"); + } + + /** + * Checks if Grav is currently symbolically linked + * + * @return boolean True if Grav is symlinked, False otherwise. + */ + + public function isSymlink() + { + return $this->remote->isSymlink(); + } +} diff --git a/system/src/Grav/Common/Getters.php b/system/src/Grav/Common/Getters.php new file mode 100644 index 0000000..8db51d3 --- /dev/null +++ b/system/src/Grav/Common/Getters.php @@ -0,0 +1,160 @@ +offsetSet($offset, $value); + } + + /** + * Magic getter method + * + * @param mixed $offset Medium name value + * + * @return mixed Medium value + */ + public function __get($offset) + { + return $this->offsetGet($offset); + } + + /** + * Magic method to determine if the attribute is set + * + * @param mixed $offset Medium name value + * + * @return boolean True if the value is set + */ + public function __isset($offset) + { + return $this->offsetExists($offset); + } + + /** + * Magic method to unset the attribute + * + * @param mixed $offset The name value to unset + */ + public function __unset($offset) + { + $this->offsetUnset($offset); + } + + /** + * @param mixed $offset + * + * @return bool + */ + public function offsetExists($offset) + { + if ($this->gettersVariable) { + $var = $this->gettersVariable; + + return isset($this->{$var}[$offset]); + } else { + return isset($this->{$offset}); + } + } + + /** + * @param mixed $offset + * + * @return mixed + */ + public function offsetGet($offset) + { + if ($this->gettersVariable) { + $var = $this->gettersVariable; + + return isset($this->{$var}[$offset]) ? $this->{$var}[$offset] : null; + } else { + return isset($this->{$offset}) ? $this->{$offset} : null; + } + } + + /** + * @param mixed $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) + { + if ($this->gettersVariable) { + $var = $this->gettersVariable; + $this->{$var}[$offset] = $value; + } else { + $this->{$offset} = $value; + } + } + + /** + * @param mixed $offset + */ + public function offsetUnset($offset) + { + if ($this->gettersVariable) { + $var = $this->gettersVariable; + unset($this->{$var}[$offset]); + } else { + unset($this->{$offset}); + } + } + + /** + * @return int + */ + public function count() + { + if ($this->gettersVariable) { + $var = $this->gettersVariable; + count($this->{$var}); + } else { + count($this->toArray()); + } + } + + /** + * Returns an associative array of object properties. + * + * @return array + */ + public function toArray() + { + if ($this->gettersVariable) { + $var = $this->gettersVariable; + + return $this->{$var}; + } else { + $properties = (array)$this; + $list = []; + foreach ($properties as $property => $value) { + if ($property[0] != "\0") { + $list[$property] = $value; + } + } + + return $list; + } + } +} diff --git a/system/src/Grav/Common/Grav.php b/system/src/Grav/Common/Grav.php new file mode 100644 index 0000000..e8a9108 --- /dev/null +++ b/system/src/Grav/Common/Grav.php @@ -0,0 +1,570 @@ + $value) { + $instance->offsetSet($key, $value); + } + } + + return self::$instance; + } + + /** + * Initialize and return a Grav instance + * + * @param array $values + * + * @return static + */ + protected static function load(array $values) + { + $container = new static($values); + + $container['grav'] = $container; + + $container['debugger'] = new Debugger(); + $container['debugger']->startTimer('_services', 'Services'); + + $container->register(new LoggerServiceProvider); + + $container->register(new ErrorServiceProvider); + + $container['uri'] = function ($c) { + /** @var Grav $c */ + return new Uri($c); + }; + + $container['task'] = function ($c) { + /** @var Grav $c */ + return !empty($_POST['task']) ? $_POST['task'] : $c['uri']->param('task'); + }; + + $container['events'] = function () { + return new EventDispatcher; + }; + $container['cache'] = function ($c) { + /** @var Grav $c */ + return new Cache($c); + }; + $container['session'] = function ($c) { + /** @var Grav $c */ + return new Session($c); + }; + $container['plugins'] = function () { + return new Plugins(); + }; + $container['themes'] = function ($c) { + /** @var Grav $c */ + return new Themes($c); + }; + $container['twig'] = function ($c) { + /** @var Grav $c */ + return new Twig($c); + }; + $container['taxonomy'] = function ($c) { + /** @var Grav $c */ + return new Taxonomy($c); + }; + $container['language'] = function ($c) { + /** @var Grav $c */ + return new Language($c); + }; + + $container['pages'] = function ($c) { + /** @var Grav $c */ + return new Pages($c); + }; + + $container['assets'] = new Assets(); + + $container['page'] = function ($c) { + /** @var Grav $c */ + + /** @var Pages $pages */ + $pages = $c['pages']; + /** @var Language $language */ + $language = $c['language']; + + /** @var Uri $uri */ + $uri = $c['uri']; + + $path = $uri->path(); // Don't trim to support trailing slash default routes + $path = $path ?: '/'; + + $page = $pages->dispatch($path); + + // Redirection tests + if ($page) { + // Language-specific redirection scenarios + if ($language->enabled()) { + if ($language->isLanguageInUrl() && !$language->isIncludeDefaultLanguage()) { + $c->redirect($page->route()); + } + if (!$language->isLanguageInUrl() && $language->isIncludeDefaultLanguage()) { + $c->redirectLangSafe($page->route()); + } + } + // Default route test and redirect + if ($c['config']->get('system.pages.redirect_default_route') && $page->route() != $path) { + $c->redirectLangSafe($page->route()); + } + } + + // if page is not found, try some fallback stuff + if (!$page || !$page->routable()) { + + // Try fallback URL stuff... + $c->fallbackUrl($path); + + // If no page found, fire event + $event = $c->fireEvent('onPageNotFound'); + + if (isset($event->page)) { + $page = $event->page; + } else { + throw new \RuntimeException('Page Not Found', 404); + } + } + + return $page; + }; + + $container['output'] = function ($c) { + /** @var Grav $c */ + return $c['twig']->processSite($c['uri']->extension()); + }; + + $container['browser'] = function () { + return new Browser(); + }; + + $container->register(new StreamsServiceProvider); + $container->register(new ConfigServiceProvider); + + $container['inflector'] = new Inflector(); + + $container['debugger']->stopTimer('_services'); + + return $container; + } + + /** + * Process a request + */ + public function process() + { + /** @var Debugger $debugger */ + $debugger = $this['debugger']; + + // Load site setup and initializing streams. + $debugger->startTimer('_setup', 'Site Setup'); + $this['setup']->init(); + $this['streams']; + $debugger->stopTimer('_setup'); + + // Initialize configuration. + $debugger->startTimer('_config', 'Configuration'); + $this['config']->init(); + $debugger->stopTimer('_config'); + + // Initialize error handlers. + $this['errors']->resetHandlers(); + + // Initialize debugger. + $debugger->init(); + $debugger->startTimer('init', 'Initialize'); + $this['config']->debug(); + + // Use output buffering to prevent headers from being sent too early. + ob_start(); + if ($this['config']->get('system.cache.gzip')) { + // Enable zip/deflate with a fallback in case of if browser does not support compressing. + if (!ob_start("ob_gzhandler")) { + ob_start(); + } + } + + // Initialize the timezone. + if ($this['config']->get('system.timezone')) { + date_default_timezone_set($this['config']->get('system.timezone')); + } + + // Initialize uri, session. + $this['uri']->init(); + $this['session']->init(); + + // Initialize Locale if set and configured. + if ($this['language']->enabled() && $this['config']->get('system.languages.override_locale')) { + setlocale(LC_ALL, $this['language']->getLanguage()); + } elseif ($this['config']->get('system.default_locale')) { + setlocale(LC_ALL, $this['config']->get('system.default_locale')); + } + + $debugger->stopTimer('init'); + + $debugger->startTimer('plugins', 'Plugins'); + $this['plugins']->init(); + $this->fireEvent('onPluginsInitialized'); + $debugger->stopTimer('plugins'); + + $debugger->startTimer('themes', 'Themes'); + $this['themes']->init(); + $debugger->stopTimer('themes'); + + $task = $this['task']; + if ($task) { + $this->fireEvent('onTask.' . $task); + } + + $this['assets']->init(); + $this->fireEvent('onAssetsInitialized'); + + $debugger->startTimer('twig', 'Twig'); + $this['twig']->init(); + $debugger->stopTimer('twig'); + + $debugger->startTimer('pages', 'Pages'); + $this['pages']->init(); + $this->fireEvent('onPagesInitialized'); + $debugger->stopTimer('pages'); + $this->fireEvent('onPageInitialized'); + + $debugger->addAssets(); + + // Process whole page as required + $debugger->startTimer('render', 'Render'); + $this->output = $this['output']; + $this->fireEvent('onOutputGenerated'); + $debugger->stopTimer('render'); + + // Set the header type + $this->header(); + echo $this->output; + $debugger->render(); + + $this->fireEvent('onOutputRendered'); + + register_shutdown_function([$this, 'shutdown']); + } + + /** + * Redirect browser to another location. + * + * @param string $route Internal route. + * @param int $code Redirection code (30x) + */ + public function redirect($route, $code = null) + { + /** @var Uri $uri */ + $uri = $this['uri']; + + //Check for code in route + $regex = '/.*(\[(30[1-7])\])$/'; + preg_match($regex, $route, $matches); + if ($matches) { + $route = str_replace($matches[1], '', $matches[0]); + $code = $matches[2]; + } + + if ($code === null) { + $code = $this['config']->get('system.pages.redirect_default_code', 301); + } + + if (isset($this['session'])) { + $this['session']->close(); + } + + if ($uri->isExternal($route)) { + $url = $route; + } else { + $url = rtrim($uri->rootUrl(), '/') . '/'; + + if ($this['config']->get('system.pages.redirect_trailing_slash', true)) { + $url .= trim($route, '/'); // Remove trailing slash + } else { + $url .= ltrim($route, '/'); // Support trailing slash default routes + } + } + + header("Location: {$url}", true, $code); + exit(); + } + + /** + * Redirect browser to another location taking language into account (preferred) + * + * @param string $route Internal route. + * @param int $code Redirection code (30x) + */ + public function redirectLangSafe($route, $code = null) + { + /** @var Language $language */ + $language = $this['language']; + + if (!$this['uri']->isExternal($route) && $language->enabled() && $language->isIncludeDefaultLanguage()) { + $this->redirect($language->getLanguage() . $route, $code); + } else { + $this->redirect($route, $code); + } + } + + /** + * Returns mime type for the file format. + * + * @param string $format + * + * @return string + */ + public function mime($format) + { + switch ($format) { + case 'json': + return 'application/json'; + case 'html': + return 'text/html'; + case 'atom': + return 'application/atom+xml'; + case 'rss': + return 'application/rss+xml'; + case 'xml': + return 'application/xml'; + } + + return 'text/html'; + } + + /** + * Set response header. + */ + public function header() + { + $extension = $this['uri']->extension(); + + /** @var Page $page */ + $page = $this['page']; + + header('Content-type: ' . $this->mime($extension)); + + // Calculate Expires Headers if set to > 0 + $expires = $page->expires(); + + if ($expires > 0) { + $expires_date = gmdate('D, d M Y H:i:s', time() + $expires) . ' GMT'; + header('Cache-Control: max-age=' . $expires); + header('Expires: ' . $expires_date); + } + + // Set the last modified time + if ($page->lastModified()) { + $last_modified_date = gmdate('D, d M Y H:i:s', $page->modified()) . ' GMT'; + header('Last-Modified: ' . $last_modified_date); + } + + // Calculate a Hash based on the raw file + if ($page->eTag()) { + header('ETag: ' . md5($page->raw() . $page->modified())); + } + + // Set debugger data in headers + if (!($extension === null || $extension == 'html')) { + $this['debugger']->enabled(false); + } + + // Set HTTP response code + if (isset($this['page']->header()->http_response_code)) { + http_response_code($this['page']->header()->http_response_code); + } + + // Vary: Accept-Encoding + if ($this['config']->get('system.pages.vary_accept_encoding', false)) { + header('Vary: Accept-Encoding'); + } + } + + /** + * Fires an event with optional parameters. + * + * @param string $eventName + * @param Event $event + * + * @return Event + */ + public function fireEvent($eventName, Event $event = null) + { + /** @var EventDispatcher $events */ + $events = $this['events']; + + return $events->dispatch($eventName, $event); + } + + /** + * Set the final content length for the page and flush the buffer + * + */ + public function shutdown() + { + // Prevent user abort allowing onShutdown event to run without interruptions. + if (function_exists('ignore_user_abort')) { + @ignore_user_abort(true); + } + + // Close the session allowing new requests to be handled. + if (isset($this['session'])) { + $this['session']->close(); + } + + if ($this['config']->get('system.debugger.shutdown.close_connection', true)) { + // Flush the response and close the connection to allow time consuming tasks to be performed without leaving + // the connection to the client open. This will make page loads to feel much faster. + + // FastCGI allows us to flush all response data to the client and finish the request. + $success = function_exists('fastcgi_finish_request') ? @fastcgi_finish_request() : false; + + if (!$success) { + // Unfortunately without FastCGI there is no way to close the connection. We need to ask browser to + // close the connection for us. + + if ($this['config']->get('system.cache.gzip')) { + // Flush gzhandler buffer if gzip setting was enabled. + ob_end_flush(); + + } else { + // Without gzip we have no other choice than to prevent server from compressing the output. + // This action turns off mod_deflate which would prevent us from closing the connection. + header('Content-Encoding: none'); + } + + // Get length and close the connection. + header('Content-Length: ' . ob_get_length()); + header("Connection: close"); + + // Finally flush the regular buffer. + ob_end_flush(); + @ob_flush(); + flush(); + } + } + + // Run any time consuming tasks. + $this->fireEvent('onShutdown'); + } + + /** + * This attempts to find media, other files, and download them + * + * @param $path + */ + protected function fallbackUrl($path) + { + /** @var Uri $uri */ + $uri = $this['uri']; + + /** @var Config $config */ + $config = $this['config']; + + $uri_extension = $uri->extension(); + $fallback_types = $config->get('system.media.allowed_fallback_types', null); + $supported_types = $config->get('media'); + + // Check whitelist first, then ensure extension is a valid media type + if (!empty($fallback_types) && !in_array($uri_extension, $fallback_types)) { + return; + } elseif (!array_key_exists($uri_extension, $supported_types)) { + return; + } + + $path_parts = pathinfo($path); + + /** @var Page $page */ + $page = $this['pages']->dispatch($path_parts['dirname'], true); + + if ($page) { + $media = $page->media()->all(); + $parsed_url = parse_url(rawurldecode($uri->basename())); + $media_file = $parsed_url['path']; + + // if this is a media object, try actions first + if (isset($media[$media_file])) { + /** @var Medium $medium */ + $medium = $media[$media_file]; + foreach ($uri->query(null, true) as $action => $params) { + if (in_array($action, ImageMedium::$magic_actions)) { + call_user_func_array([&$medium, $action], explode(',', $params)); + } + } + Utils::download($medium->path(), false); + } + + // unsupported media type, try to download it... + if ($uri_extension) { + $extension = $uri_extension; + } else { + if (isset($path_parts['extension'])) { + $extension = $path_parts['extension']; + } else { + $extension = null; + } + } + + if ($extension) { + $download = true; + if (in_array(ltrim($extension, '.'), $config->get('system.media.unsupported_inline_types', []))) { + $download = false; + } + Utils::download($page->path() . DIRECTORY_SEPARATOR . $uri->basename(), $download); + } + } + } +} diff --git a/system/src/Grav/Common/GravTrait.php b/system/src/Grav/Common/GravTrait.php new file mode 100644 index 0000000..a8f8682 --- /dev/null +++ b/system/src/Grav/Common/GravTrait.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +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 + */ + public static function truncate($html, $length, $opts=array()) + { + 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 ($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; + } + 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; + } + + protected static function truncateNode($doc, $node, $length, $opts) + { + 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); + } + $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; + } + + // unhandle the ampersand + $txt = static::xmlUnescape($txt); + + $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; + } + break; + } + } + return array($inner, $remaining, $opts); + } + + protected static function truncateText($node, $length, $opts) + { + $string = $node->textContent; + + 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']; + } + } + 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); + } + return array(implode('', array_slice($words, 0, $length)), $count, $opts); + } + } + + protected static function ellipsable($node) + { + return ($node instanceof DOMDocument) + || in_array(mb_strtolower($node->nodeName), static::$ellipsable_tags) + ; + } + + protected static function xmlEscape($string) + { + $string = str_replace('&', '&', $string); + $string = str_replace('plural)) { + $language = self::getGrav()['language']; + $this->plural = $language->translate('INFLECTOR_PLURALS', null, true); + $this->singular = $language->translate('INFLECTOR_SINGULAR', null, true); + $this->uncountable = $language->translate('INFLECTOR_UNCOUNTABLE', null, true); + $this->irregular = $language->translate('INFLECTOR_IRREGULAR', null, true); + $this->ordinals = $language->translate('INFLECTOR_ORDINALS', null, true); + } + } + + /** + * Pluralizes English nouns. + * + * @param string $word English noun to pluralize + * @param int $count The count + * + * @return string Plural noun + */ + public function pluralize($word, $count = 2) + { + $this->init(); + + if ($count == 1) { + return $word; + } + + $lowercased_word = strtolower($word); + + foreach ($this->uncountable as $_uncountable) { + if (substr($lowercased_word, (-1 * strlen($_uncountable))) == $_uncountable) { + return $word; + } + } + + foreach ($this->irregular as $_plural => $_singular) { + if (preg_match('/(' . $_plural . ')$/i', $word, $arr)) { + return preg_replace('/(' . $_plural . ')$/i', substr($arr[0], 0, 1) . substr($_singular, 1), $word); + } + } + + foreach ($this->plural as $rule => $replacement) { + if (preg_match($rule, $word)) { + return preg_replace($rule, $replacement, $word); + } + } + + return false; + + } + + /** + * Singularizes English nouns. + * + * @param string $word English noun to singularize + * @param int $count + * + * @return string Singular noun. + */ + public function singularize($word, $count = 1) + { + $this->init(); + + if ($count != 1) { + return $word; + } + + $lowercased_word = strtolower($word); + foreach ($this->uncountable as $_uncountable) { + if (substr($lowercased_word, (-1 * strlen($_uncountable))) == $_uncountable) { + return $word; + } + } + + foreach ($this->irregular as $_plural => $_singular) { + if (preg_match('/(' . $_singular . ')$/i', $word, $arr)) { + return preg_replace('/(' . $_singular . ')$/i', substr($arr[0], 0, 1) . substr($_plural, 1), $word); + } + } + + foreach ($this->singular as $rule => $replacement) { + if (preg_match($rule, $word)) { + return preg_replace($rule, $replacement, $word); + } + } + + return $word; + } + + /** + * Converts an underscored or CamelCase word into a English + * sentence. + * + * The titleize public function converts text like "WelcomePage", + * "welcome_page" or "welcome page" to this "Welcome + * Page". + * If second parameter is set to 'first' it will only + * capitalize the first character of the title. + * + * @param string $word Word to format as tile + * @param string $uppercase If set to 'first' it will only uppercase the + * first character. Otherwise it will uppercase all + * the words in the title. + * + * @return string Text formatted as title + */ + public function titleize($word, $uppercase = '') + { + $uppercase = $uppercase == 'first' ? 'ucfirst' : 'ucwords'; + + return $uppercase($this->humanize($this->underscorize($word))); + } + + /** + * Returns given word as CamelCased + * + * Converts a word like "send_email" to "SendEmail". It + * will remove non alphanumeric character from the word, so + * "who's online" will be converted to "WhoSOnline" + * + * @see variablize + * + * @param string $word Word to convert to camel case + * + * @return string UpperCamelCasedWord + */ + public function camelize($word) + { + return str_replace(' ', '', ucwords(preg_replace('/[^A-Z^a-z^0-9]+/', ' ', $word))); + } + + /** + * Converts a word "into_it_s_underscored_version" + * + * Convert any "CamelCased" or "ordinary Word" into an + * "underscored_word". + * + * This can be really useful for creating friendly URLs. + * + * @param string $word Word to underscore + * + * @return string Underscored word + */ + public function underscorize($word) + { + $regex1 = preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1_\2', $word); + $regex2 = preg_replace('/([a-zd])([A-Z])/', '\1_\2', $regex1); + $regex3 = preg_replace('/[^A-Z^a-z^0-9]+/', '_', $regex2); + + return strtolower($regex3); + } + + /** + * Converts a word "into-it-s-hyphenated-version" + * + * Convert any "CamelCased" or "ordinary Word" into an + * "hyphenated-word". + * + * This can be really useful for creating friendly URLs. + * + * @param string $word Word to hyphenate + * + * @return string hyphenized word + */ + public function hyphenize($word) + { + $regex1 = preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1-\2', $word); + $regex2 = preg_replace('/([a-zd])([A-Z])/', '\1-\2', $regex1); + $regex3 = preg_replace('/[^A-Z^a-z^0-9]+/', '-', $regex2); + + return strtolower($regex3); + } + + /** + * Returns a human-readable string from $word + * + * Returns a human-readable string from $word, by replacing + * underscores with a space, and by upper-casing the initial + * character by default. + * + * If you need to uppercase all the words you just have to + * pass 'all' as a second parameter. + * + * @param string $word String to "humanize" + * @param string $uppercase If set to 'all' it will uppercase all the words + * instead of just the first one. + * + * @return string Human-readable word + */ + public function humanize($word, $uppercase = '') + { + $uppercase = $uppercase == 'all' ? 'ucwords' : 'ucfirst'; + + return $uppercase(str_replace('_', ' ', preg_replace('/_id$/', '', $word))); + } + + /** + * Same as camelize but first char is underscored + * + * Converts a word like "send_email" to "sendEmail". It + * will remove non alphanumeric character from the word, so + * "who's online" will be converted to "whoSOnline" + * + * @see camelize + * + * @param string $word Word to lowerCamelCase + * + * @return string Returns a lowerCamelCasedWord + */ + public function variablize($word) + { + $word = $this->camelize($word); + + return strtolower($word[0]) . substr($word, 1); + } + + /** + * Converts a class name to its table name according to rails + * naming conventions. + * + * Converts "Person" to "people" + * + * @see classify + * + * @param string $class_name Class name for getting related table_name. + * + * @return string plural_table_name + */ + public function tableize($class_name) + { + return $this->pluralize($this->underscorize($class_name)); + } + + /** + * Converts a table name to its class name according to rails + * naming conventions. + * + * Converts "people" to "Person" + * + * @see tableize + * + * @param string $table_name Table name for getting related ClassName. + * + * @return string SingularClassName + */ + public function classify($table_name) + { + return $this->camelize($this->singularize($table_name)); + } + + /** + * Converts number to its ordinal English form. + * + * This method converts 13 to 13th, 2 to 2nd ... + * + * @param integer $number Number to get its ordinal value + * + * @return string Ordinal representation of given string. + */ + public function ordinalize($number) + { + $this->init(); + + if (in_array(($number % 100), range(11, 13))) { + return $number . $this->ordinals['default']; + } else { + switch (($number % 10)) { + case 1: + return $number . $this->ordinals['first']; + break; + case 2: + return $number . $this->ordinals['second']; + break; + case 3: + return $number . $this->ordinals['third']; + break; + default: + return $number . $this->ordinals['default']; + break; + } + } + } + + /** + * Converts a number of days to a number of months + * + * @param int $days + * + * @return int + */ + public function monthize($days) + { + $now = new \DateTime(); + $end = new \DateTime(); + + $duration = new \DateInterval("P{$days}D"); + + $diff = $end->add($duration)->diff($now); + + // handle years + if ($diff->y > 0) { + $diff->m = $diff->m + 12 * $diff->y; + } + + return $diff->m; + } +} diff --git a/system/src/Grav/Common/Iterator.php b/system/src/Grav/Common/Iterator.php new file mode 100644 index 0000000..9afd9db --- /dev/null +++ b/system/src/Grav/Common/Iterator.php @@ -0,0 +1,256 @@ +items[$key])) ? $this->items[$key] : null; + } + + /** + * Clone the iterator. + */ + public function __clone() + { + foreach ($this as $key => $value) { + if (is_object($value)) { + $this->$key = clone $this->$key; + } + } + } + + /** + * Convents iterator to a comma separated list. + * + * @return string + */ + public function __toString() + { + return implode(',', $this->items); + } + + /** + * Remove item from the list. + * + * @param $key + */ + public function remove($key) + { + $this->offsetUnset($key); + } + + /** + * Return previous item. + * + * @return mixed + */ + public function prev() + { + return prev($this->items); + } + + /** + * Return nth item. + * + * @param int $key + * + * @return mixed|bool + */ + public function nth($key) + { + $items = array_keys($this->items); + + return (isset($items[$key])) ? $this->offsetGet($items[$key]) : false; + } + + /** + * Get the first item + * + * @return mixed + */ + public function first() + { + $items = array_keys($this->items); + + return $this->offsetGet(array_shift($items)); + } + + /** + * Get the last item + * + * @return mixed + */ + public function last() + { + $items = array_keys($this->items); + + return $this->offsetGet(array_pop($items)); + } + + /** + * Reverse the Iterator + * + * @return $this + */ + public function reverse() + { + $this->items = array_reverse($this->items); + + return $this; + } + + /** + * @param mixed $needle Searched value. + * + * @return string|bool Key if found, otherwise false. + */ + public function indexOf($needle) + { + foreach (array_values($this->items) as $key => $value) { + if ($value === $needle) { + return $key; + } + } + + return false; + } + + /** + * Shuffle items. + * + * @return $this + */ + public function shuffle() + { + $keys = array_keys($this->items); + shuffle($keys); + + $new = []; + foreach ($keys as $key) { + $new[$key] = $this->items[$key]; + } + + $this->items = $new; + + return $this; + } + + /** + * Slice the list. + * + * @param int $offset + * @param int $length + * + * @return $this + */ + public function slice($offset, $length = null) + { + $this->items = array_slice($this->items, $offset, $length); + + return $this; + } + + /** + * Pick one or more random entries. + * + * @param int $num Specifies how many entries should be picked. + * + * @return $this + */ + public function random($num = 1) + { + $this->items = array_intersect_key($this->items, array_flip((array)array_rand($this->items, $num))); + + return $this; + } + + /** + * Append new elements to the list. + * + * @param array|Iterator $items Items to be appended. Existing keys will be overridden with the new values. + * + * @return $this + */ + public function append($items) + { + if ($items instanceof static) { + $items = $items->toArray(); + } + $this->items = array_merge($this->items, (array)$items); + + return $this; + } + + /** + * Filter elements from the list + * + * @param callable|null $callback A function the receives ($value, $key) and must return a boolean to indicate + * filter status + * + * @return $this + */ + public function filter(callable $callback = null) + { + foreach ($this->items as $key => $value) { + if ( + ($callback && !call_user_func($callback, $value, $key)) || + (!$callback && !(bool)$value) + ) { + unset($this->items[$key]); + } + } + + 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 new file mode 100644 index 0000000..eb69adc --- /dev/null +++ b/system/src/Grav/Common/Language/Language.php @@ -0,0 +1,475 @@ +grav = $grav; + $this->config = $grav['config']; + $this->languages = $this->config->get('system.languages.supported', []); + $this->init(); + } + + /** + * Initialize the default and enabled languages + */ + public function init() + { + $this->default = reset($this->languages); + + if (empty($this->languages)) { + $this->enabled = false; + } + } + + /** + * Ensure that languages are enabled + * + * @return bool + */ + public function enabled() + { + return $this->enabled; + } + + /** + * Gets the array of supported languages + * + * @return array + */ + public function getLanguages() + { + return $this->languages; + } + + /** + * Sets the current supported languages manually + * + * @param $langs + */ + public function setLanguages($langs) + { + $this->languages = $langs; + $this->init(); + } + + /** + * Gets a pipe-separated string of available languages + * + * @return string + */ + public function getAvailable() + { + $languagesArray = $this->languages; //Make local copy + sort($languagesArray); + return implode('|', array_reverse($languagesArray)); + } + + /** + * Gets language, active if set, else default + * + * @return mixed + */ + public function getLanguage() + { + return $this->active ? $this->active : $this->default; + } + + /** + * Gets current default language + * + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * Sets default language manually + * + * @param $lang + * + * @return bool + */ + public function setDefault($lang) + { + if ($this->validate($lang)) { + $this->default = $lang; + + return $lang; + } + + return false; + } + + /** + * Gets current active language + * + * @return mixed + */ + public function getActive() + { + return $this->active; + } + + /** + * Sets active language manually + * + * @param $lang + * + * @return bool + */ + public function setActive($lang) + { + if ($this->validate($lang)) { + $this->active = $lang; + + return $lang; + } + + return false; + } + + /** + * Sets the active language based on the first part of the URL + * + * @param $uri + * + * @return mixed + */ + public function setActiveFromUri($uri) + { + $regex = '/(^\/(' . $this->getAvailable() . '))(?:\/.*|$)/i'; + + // if languages set + if ($this->enabled()) { + // try setting from prefix of URL (/en/blah/blah) + if (preg_match($regex, $uri, $matches)) { + $this->lang_in_url = true; + $this->active = $matches[2]; + $uri = preg_replace("/\\" . $matches[1] . "/", '', $matches[0], 1); + + // store in session if different + if ($this->config->get('system.session.enabled', false) + && $this->config->get('system.languages.session_store_active', true) + && $this->grav['session']->active_language != $this->active + ) { + $this->grav['session']->active_language = $this->active; + } + } else { + // try getting from session, else no active + if ($this->config->get('system.session.enabled', false) && + $this->config->get('system.languages.session_store_active', true)) { + $this->active = $this->grav['session']->active_language ?: null; + } + // if still null, try from http_accept_language header + if ($this->active === null && $this->config->get('system.languages.http_accept_language')) { + $preferred = $this->getBrowserLanguages(); + foreach ($preferred as $lang) { + if ($this->validate($lang)) { + $this->active = $lang; + break; + } + } + + } + } + } + + return $uri; + } + + /** + * Get's a URL prefix based on configuration + * + * @param null $lang + * @return string + */ + public function getLanguageURLPrefix($lang = null) + { + // if active lang is not passed in, use current active + if (!$lang) { + $lang = $this->getLanguage(); + } + + return $this->isIncludeDefaultLanguage($lang) ? '/' . $lang : ''; + } + + /** + * Test to see if language is default and language should be included in the URL + * + * @param null $lang + * @return bool + */ + public function isIncludeDefaultLanguage($lang = null) + { + // if active lang is not passed in, use current active + if (!$lang) { + $lang = $this->getLanguage(); + } + + if ($this->default == $lang && $this->config->get('system.languages.include_default_lang') === false) { + return false; + } else { + return true; + } + } + + /** + * Simple getter to tell if a language was found in the URL + * + * @return bool + */ + public function isLanguageInUrl() + { + return (bool) $this->lang_in_url; + } + + + /** + * Gets an array of valid extensions with active first, then fallback extensions + * + * @param string|null $file_ext + * + * @return array + */ + public function getFallbackPageExtensions($file_ext = null) + { + if (empty($this->page_extensions)) { + if (empty($file_ext)) { + $file_ext = CONTENT_EXT; + } + + if ($this->enabled()) { + $valid_lang_extensions = []; + foreach ($this->languages as $lang) { + $valid_lang_extensions[] = '.' . $lang . $file_ext; + } + + if ($this->active) { + $active_extension = '.' . $this->active . $file_ext; + $key = array_search($active_extension, $valid_lang_extensions); + unset($valid_lang_extensions[$key]); + array_unshift($valid_lang_extensions, $active_extension); + } + + $this->page_extensions = array_merge($valid_lang_extensions, (array)$file_ext); + } else { + $this->page_extensions = (array)$file_ext; + } + } + + return $this->page_extensions; + } + + /** + * Gets an array of languages with active first, then fallback languages + * + * @return array + */ + public function getFallbackLanguages() + { + if (empty($this->fallback_languages)) { + if ($this->enabled()) { + $fallback_languages = $this->languages; + + if ($this->active) { + $active_extension = $this->active; + $key = array_search($active_extension, $fallback_languages); + unset($fallback_languages[$key]); + array_unshift($fallback_languages, $active_extension); + } + $this->fallback_languages = $fallback_languages; + } + // always add english in case a translation doesn't exist + $this->fallback_languages[] = 'en'; + } + + return $this->fallback_languages; + } + + /** + * Ensures the language is valid and supported + * + * @param $lang + * + * @return bool + */ + public function validate($lang) + { + if (in_array($lang, $this->languages)) { + return true; + } + + return false; + } + + /** + * Translate a key and possibly arguments into a string using current lang and fallbacks + * + * @param mixed $args The first argument is the lookup key value + * Other arguments can be passed and replaced in the translation with sprintf syntax + * @param array $languages + * @param bool $array_support + * @param bool $html_out + * + * @return string + */ + public function translate($args, array $languages = null, $array_support = false, $html_out = false) + { + if (is_array($args)) { + $lookup = array_shift($args); + } else { + $lookup = $args; + $args = []; + } + + if ($this->config->get('system.languages.translations', true)) { + if ($this->enabled() && $lookup) { + if (empty($languages)) { + if ($this->config->get('system.languages.translations_fallback', true)) { + $languages = $this->getFallbackLanguages(); + } else { + $languages = (array)$this->getLanguage(); + } + } + } else { + $languages = ['en']; + } + + foreach ((array)$languages as $lang) { + $translation = $this->getTranslation($lang, $lookup, $array_support); + + if ($translation) { + if (count($args) >= 1) { + return vsprintf($translation, $args); + } else { + return $translation; + } + } + } + } + + if ($html_out) { + return '' . $lookup . ''; + } else { + return $lookup; + } + } + + /** + * Translate Array + * + * @param $key + * @param $index + * @param null $languages + * @param bool $html_out + * + * @return string + */ + public function translateArray($key, $index, $languages = null, $html_out = false) + { + if ($this->config->get('system.languages.translations', true)) { + if ($this->enabled() && $key) { + if (empty($languages)) { + if ($this->config->get('system.languages.translations_fallback', true)) { + $languages = $this->getFallbackLanguages(); + } else { + $languages = (array)$this->getDefault(); + } + } + } else { + $languages = ['en']; + } + + foreach ((array)$languages as $lang) { + $translation_array = (array)$this->config->getLanguages()->get($lang . '.' . $key, null); + if ($translation_array && array_key_exists($index, $translation_array)) { + return $translation_array[$index]; + } + } + } + + if ($html_out) { + return '' . $key . '[' . $index . ']'; + } else { + return $key . '[' . $index . ']'; + } + } + + /** + * Lookup the translation text for a given lang and key + * + * @param string $lang lang code + * @param string $key key to lookup with + * @param bool $array_support + * + * @return string + */ + public function getTranslation($lang, $key, $array_support = false) + { + $translation = $this->config->getLanguages()->get($lang . '.' . $key, null); + if (!$array_support && is_array($translation)) { + return (string)array_shift($translation); + } + + return $translation; + } + + /** + * Get the browser accepted languages + * + * @param array $accept_langs + * + * @return array + */ + public function getBrowserLanguages($accept_langs = []) + { + if (empty($this->http_accept_language)) { + if (empty($accept_langs) && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { + $accept_langs = $_SERVER['HTTP_ACCEPT_LANGUAGE']; + } else { + return $accept_langs; + } + + foreach (explode(',', $accept_langs) as $k => $pref) { + // split $pref again by ';q=' + // and decorate the language entries by inverted position + if (false !== ($i = strpos($pref, ';q='))) { + $langs[substr($pref, 0, $i)] = [(float)substr($pref, $i + 3), -$k]; + } else { + $langs[$pref] = [1, -$k]; + } + } + arsort($langs); + + // no need to undecorate, because we're only interested in the keys + $this->http_accept_language = array_keys($langs); + } + return $this->http_accept_language; + } + +} diff --git a/system/src/Grav/Common/Language/LanguageCodes.php b/system/src/Grav/Common/Language/LanguageCodes.php new file mode 100644 index 0000000..86b130a --- /dev/null +++ b/system/src/Grav/Common/Language/LanguageCodes.php @@ -0,0 +1,774 @@ + [ + "name" => "Abkhaz", + "nativeName" => "аҧсуа" + ], + "aa" => [ + "name" => "Afar", + "nativeName" => "Afaraf" + ], + "af" => [ + "name" => "Afrikaans", + "nativeName" => "Afrikaans" + ], + "ak" => [ + "name" => "Akan", + "nativeName" => "Akan" + ], + "sq" => [ + "name" => "Albanian", + "nativeName" => "Shqip" + ], + "am" => [ + "name" => "Amharic", + "nativeName" => "አማርኛ" + ], + "ar" => [ + "name" => "Arabic", + "nativeName" => "العربية" + ], + "an" => [ + "name" => "Aragonese", + "nativeName" => "Aragonés" + ], + "hy" => [ + "name" => "Armenian", + "nativeName" => "Հայերեն" + ], + "as" => [ + "name" => "Assamese", + "nativeName" => "অসমীয়া" + ], + "av" => [ + "name" => "Avaric", + "nativeName" => "авар мацӀ" + ], + "ae" => [ + "name" => "Avestan", + "nativeName" => "avesta" + ], + "ay" => [ + "name" => "Aymara", + "nativeName" => "aymar aru" + ], + "az" => [ + "name" => "Azerbaijani", + "nativeName" => "azərbaycan dili" + ], + "bm" => [ + "name" => "Bambara", + "nativeName" => "bamanankan" + ], + "ba" => [ + "name" => "Bashkir", + "nativeName" => "башҡорт теле" + ], + "eu" => [ + "name" => "Basque", + "nativeName" => "euskara" + ], + "be" => [ + "name" => "Belarusian", + "nativeName" => "Беларуская" + ], + "bn" => [ + "name" => "Bengali", + "nativeName" => "বাংলা" + ], + "bh" => [ + "name" => "Bihari", + "nativeName" => "भोजपुरी" + ], + "bi" => [ + "name" => "Bislama", + "nativeName" => "Bislama" + ], + "bs" => [ + "name" => "Bosnian", + "nativeName" => "bosanski jezik" + ], + "br" => [ + "name" => "Breton", + "nativeName" => "brezhoneg" + ], + "bg" => [ + "name" => "Bulgarian", + "nativeName" => "български език" + ], + "my" => [ + "name" => "Burmese", + "nativeName" => "ဗမာစာ" + ], + "ca" => [ + "name" => "Catalan", + "nativeName" => "Català" + ], + "ch" => [ + "name" => "Chamorro", + "nativeName" => "Chamoru" + ], + "ce" => [ + "name" => "Chechen", + "nativeName" => "нохчийн мотт" + ], + "ny" => [ + "name" => "Chichewa", + "nativeName" => "chiCheŵa" + ], + "zh" => [ + "name" => "Chinese", + "nativeName" => "中文" + ], + "cv" => [ + "name" => "Chuvash", + "nativeName" => "чӑваш чӗлхи" + ], + "kw" => [ + "name" => "Cornish", + "nativeName" => "Kernewek" + ], + "co" => [ + "name" => "Corsican", + "nativeName" => "corsu" + ], + "cr" => [ + "name" => "Cree", + "nativeName" => "ᓀᐦᐃᔭᐍᐏᐣ" + ], + "hr" => [ + "name" => "Croatian", + "nativeName" => "hrvatski" + ], + "cs" => [ + "name" => "Czech", + "nativeName" => "česky" + ], + "da" => [ + "name" => "Danish", + "nativeName" => "dansk" + ], + "dv" => [ + "name" => "Divehi", + "nativeName" => "ދިވެހި" + ], + "nl" => [ + "name" => "Dutch", + "nativeName" => "Nederlands" + ], + "en" => [ + "name" => "English", + "nativeName" => "English" + ], + "eo" => [ + "name" => "Esperanto", + "nativeName" => "Esperanto" + ], + "et" => [ + "name" => "Estonian", + "nativeName" => "eesti" + ], + "ee" => [ + "name" => "Ewe", + "nativeName" => "Eʋegbe" + ], + "fo" => [ + "name" => "Faroese", + "nativeName" => "føroyskt" + ], + "fj" => [ + "name" => "Fijian", + "nativeName" => "vosa Vakaviti" + ], + "fi" => [ + "name" => "Finnish", + "nativeName" => "suomi" + ], + "fr" => [ + "name" => "French", + "nativeName" => "Français" + ], + "ff" => [ + "name" => "Fula", + "nativeName" => "Fulfulde" + ], + "gl" => [ + "name" => "Galician", + "nativeName" => "Galego" + ], + "ka" => [ + "name" => "Georgian", + "nativeName" => "ქართული" + ], + "de" => [ + "name" => "German", + "nativeName" => "Deutsch" + ], + "el" => [ + "name" => "Greek", + "nativeName" => "Ελληνικά" + ], + "gn" => [ + "name" => "Guaraní", + "nativeName" => "Avañeẽ" + ], + "gu" => [ + "name" => "Gujarati", + "nativeName" => "ગુજરાતી" + ], + "ht" => [ + "name" => "Haitian", + "nativeName" => "Kreyòl ayisyen" + ], + "ha" => [ + "name" => "Hausa", + "nativeName" => "هَوُسَ" + ], + "he" => [ + "name" => "Hebrew", + "nativeName" => "עברית" + ], + "hz" => [ + "name" => "Herero", + "nativeName" => "Otjiherero" + ], + "hi" => [ + "name" => "Hindi", + "nativeName" => "हिन्दी" + ], + "ho" => [ + "name" => "Hiri Motu", + "nativeName" => "Hiri Motu" + ], + "hu" => [ + "name" => "Hungarian", + "nativeName" => "Magyar" + ], + "ia" => [ + "name" => "Interlingua", + "nativeName" => "Interlingua" + ], + "id" => [ + "name" => "Indonesian", + "nativeName" => "Bahasa Indonesia" + ], + "ie" => [ + "name" => "Interlingue", + "nativeName" => "Interlingue" + ], + "ga" => [ + "name" => "Irish", + "nativeName" => "Gaeilge" + ], + "ig" => [ + "name" => "Igbo", + "nativeName" => "Asụsụ Igbo" + ], + "ik" => [ + "name" => "Inupiaq", + "nativeName" => "Iñupiaq" + ], + "io" => [ + "name" => "Ido", + "nativeName" => "Ido" + ], + "is" => [ + "name" => "Icelandic", + "nativeName" => "Íslenska" + ], + "it" => [ + "name" => "Italian", + "nativeName" => "Italiano" + ], + "iu" => [ + "name" => "Inuktitut", + "nativeName" => "ᐃᓄᒃᑎᑐᑦ" + ], + "ja" => [ + "name" => "Japanese", + "nativeName" => "日本語" + ], + "jv" => [ + "name" => "Javanese", + "nativeName" => "basa Jawa" + ], + "kl" => [ + "name" => "Kalaallisut", + "nativeName" => "kalaallisut" + ], + "kn" => [ + "name" => "Kannada", + "nativeName" => "ಕನ್ನಡ" + ], + "kr" => [ + "name" => "Kanuri", + "nativeName" => "Kanuri" + ], + "ks" => [ + "name" => "Kashmiri", + "nativeName" => "कश्मीरी" + ], + "kk" => [ + "name" => "Kazakh", + "nativeName" => "Қазақ тілі" + ], + "km" => [ + "name" => "Khmer", + "nativeName" => "ភាសាខ្មែរ" + ], + "ki" => [ + "name" => "Kikuyu", + "nativeName" => "Gĩkũyũ" + ], + "rw" => [ + "name" => "Kinyarwanda", + "nativeName" => "Ikinyarwanda" + ], + "ky" => [ + "name" => "Kirghiz", + "nativeName" => "кыргыз тили" + ], + "kv" => [ + "name" => "Komi", + "nativeName" => "коми кыв" + ], + "kg" => [ + "name" => "Kongo", + "nativeName" => "KiKongo" + ], + "ko" => [ + "name" => "Korean", + "nativeName" => "한국어" + ], + "ku" => [ + "name" => "Kurdish", + "nativeName" => "كوردی‎" + ], + "kj" => [ + "name" => "Kwanyama", + "nativeName" => "Kuanyama" + ], + "la" => [ + "name" => "Latin", + "nativeName" => "latine" + ], + "lb" => [ + "name" => "Luxembourgish", + "nativeName" => "Lëtzebuergesch" + ], + "lg" => [ + "name" => "Luganda", + "nativeName" => "Luganda" + ], + "li" => [ + "name" => "Limburgish", + "nativeName" => "Limburgs" + ], + "ln" => [ + "name" => "Lingala", + "nativeName" => "Lingála" + ], + "lo" => [ + "name" => "Lao", + "nativeName" => "ພາສາລາວ" + ], + "lt" => [ + "name" => "Lithuanian", + "nativeName" => "lietuvių kalba" + ], + "lu" => [ + "name" => "Luba-Katanga", + "nativeName" => "Luba-Katanga" + ], + "lv" => [ + "name" => "Latvian", + "nativeName" => "latviešu valoda" + ], + "gv" => [ + "name" => "Manx", + "nativeName" => "Gaelg" + ], + "mk" => [ + "name" => "Macedonian", + "nativeName" => "македонски јазик" + ], + "mg" => [ + "name" => "Malagasy", + "nativeName" => "Malagasy fiteny" + ], + "ms" => [ + "name" => "Malay", + "nativeName" => "بهاس ملايو‎" + ], + "ml" => [ + "name" => "Malayalam", + "nativeName" => "മലയാളം" + ], + "mt" => [ + "name" => "Maltese", + "nativeName" => "Malti" + ], + "mi" => [ + "name" => "Māori", + "nativeName" => "te reo Māori" + ], + "mr" => [ + "name" => "Marathi", + "nativeName" => "मराठी" + ], + "mh" => [ + "name" => "Marshallese", + "nativeName" => "Kajin M̧ajeļ" + ], + "mn" => [ + "name" => "Mongolian", + "nativeName" => "монгол" + ], + "na" => [ + "name" => "Nauru", + "nativeName" => "Ekakairũ Naoero" + ], + "nv" => [ + "name" => "Navajo", + "nativeName" => "Diné bizaad" + ], + "nb" => [ + "name" => "Norwegian Bokmål", + "nativeName" => "Norsk bokmål" + ], + "nd" => [ + "name" => "North Ndebele", + "nativeName" => "isiNdebele" + ], + "ne" => [ + "name" => "Nepali", + "nativeName" => "नेपाली" + ], + "ng" => [ + "name" => "Ndonga", + "nativeName" => "Owambo" + ], + "nn" => [ + "name" => "Norwegian Nynorsk", + "nativeName" => "Norsk nynorsk" + ], + "no" => [ + "name" => "Norwegian", + "nativeName" => "Norsk" + ], + "ii" => [ + "name" => "Nuosu", + "nativeName" => "ꆈꌠ꒿ Nuosuhxop" + ], + "nr" => [ + "name" => "South Ndebele", + "nativeName" => "isiNdebele" + ], + "oc" => [ + "name" => "Occitan", + "nativeName" => "Occitan" + ], + "oj" => [ + "name" => "Ojibwe, Ojibwa", + "nativeName" => "ᐊᓂᔑᓈᐯᒧᐎᓐ" + ], + "cu" => [ + "name" => "Church Slavic", + "nativeName" => "ѩзыкъ словѣньскъ" + ], + "om" => [ + "name" => "Oromo", + "nativeName" => "Afaan Oromoo" + ], + "or" => [ + "name" => "Oriya", + "nativeName" => "ଓଡ଼ିଆ" + ], + "os" => [ + "name" => "Ossetian", + "nativeName" => "ирон æвзаг" + ], + "pa" => [ + "name" => "Panjabi", + "nativeName" => "ਪੰਜਾਬੀ" + ], + "pi" => [ + "name" => "Pāli", + "nativeName" => "पाऴि" + ], + "fa" => [ + "name" => "Persian", + "nativeName" => "فارسی" + ], + "pl" => [ + "name" => "Polish", + "nativeName" => "polski" + ], + "ps" => [ + "name" => "Pashto", + "nativeName" => "پښتو" + ], + "pt" => [ + "name" => "Portuguese", + "nativeName" => "Português" + ], + "qu" => [ + "name" => "Quechua", + "nativeName" => "Runa Simi" + ], + "rm" => [ + "name" => "Romansh", + "nativeName" => "rumantsch grischun" + ], + "rn" => [ + "name" => "Kirundi", + "nativeName" => "kiRundi" + ], + "ro" => [ + "name" => "Romanian", + "nativeName" => "română" + ], + "ru" => [ + "name" => "Russian", + "nativeName" => "Русский" + ], + "sa" => [ + "name" => "Sanskrit", + "nativeName" => "संस्कृतम्" + ], + "sc" => [ + "name" => "Sardinian", + "nativeName" => "sardu" + ], + "sd" => [ + "name" => "Sindhi", + "nativeName" => "सिन्धी" + ], + "se" => [ + "name" => "Northern Sami", + "nativeName" => "Davvisámegiella" + ], + "sm" => [ + "name" => "Samoan", + "nativeName" => "gagana faa Samoa" + ], + "sg" => [ + "name" => "Sango", + "nativeName" => "yângâ tî sängö" + ], + "sr" => [ + "name" => "Serbian", + "nativeName" => "српски језик" + ], + "gd" => [ + "name" => "Scottish Gaelic", + "nativeName" => "Gàidhlig" + ], + "sn" => [ + "name" => "Shona", + "nativeName" => "chiShona" + ], + "si" => [ + "name" => "Sinhala", + "nativeName" => "සිංහල" + ], + "sk" => [ + "name" => "Slovak", + "nativeName" => "slovenčina" + ], + "sl" => [ + "name" => "Slovene", + "nativeName" => "slovenščina" + ], + "so" => [ + "name" => "Somali", + "nativeName" => "Soomaaliga" + ], + "st" => [ + "name" => "Southern Sotho", + "nativeName" => "Sesotho" + ], + "es" => [ + "name" => "Spanish", + "nativeName" => "Español" + ], + "su" => [ + "name" => "Sundanese", + "nativeName" => "Basa Sunda" + ], + "sw" => [ + "name" => "Swahili", + "nativeName" => "Kiswahili" + ], + "ss" => [ + "name" => "Swati", + "nativeName" => "SiSwati" + ], + "sv" => [ + "name" => "Swedish", + "nativeName" => "svenska" + ], + "ta" => [ + "name" => "Tamil", + "nativeName" => "தமிழ்" + ], + "te" => [ + "name" => "Telugu", + "nativeName" => "తెలుగు" + ], + "tg" => [ + "name" => "Tajik", + "nativeName" => "тоҷикӣ" + ], + "th" => [ + "name" => "Thai", + "nativeName" => "ไทย" + ], + "ti" => [ + "name" => "Tigrinya", + "nativeName" => "ትግርኛ" + ], + "bo" => [ + "name" => "Tibetan", + "nativeName" => "བོད་ཡིག" + ], + "tk" => [ + "name" => "Turkmen", + "nativeName" => "Türkmen" + ], + "tl" => [ + "name" => "Tagalog", + "nativeName" => "Wikang Tagalog" + ], + "tn" => [ + "name" => "Tswana", + "nativeName" => "Setswana" + ], + "to" => [ + "name" => "Tonga", + "nativeName" => "faka Tonga" + ], + "tr" => [ + "name" => "Turkish", + "nativeName" => "Türkçe" + ], + "ts" => [ + "name" => "Tsonga", + "nativeName" => "Xitsonga" + ], + "tt" => [ + "name" => "Tatar", + "nativeName" => "татарча" + ], + "tw" => [ + "name" => "Twi", + "nativeName" => "Twi" + ], + "ty" => [ + "name" => "Tahitian", + "nativeName" => "Reo Tahiti" + ], + "ug" => [ + "name" => "Uighur", + "nativeName" => "Uyƣurqə" + ], + "uk" => [ + "name" => "Ukrainian", + "nativeName" => "українська" + ], + "ur" => [ + "name" => "Urdu", + "nativeName" => "اردو" + ], + "uz" => [ + "name" => "Uzbek", + "nativeName" => "zbek" + ], + "ve" => [ + "name" => "Venda", + "nativeName" => "Tshivenḓa" + ], + "vi" => [ + "name" => "Vietnamese", + "nativeName" => "Tiếng Việt" + ], + "vo" => [ + "name" => "Volapük", + "nativeName" => "Volapük" + ], + "wa" => [ + "name" => "Walloon", + "nativeName" => "Walon" + ], + "cy" => [ + "name" => "Welsh", + "nativeName" => "Cymraeg" + ], + "wo" => [ + "name" => "Wolof", + "nativeName" => "Wollof" + ], + "fy" => [ + "name" => "Western Frisian", + "nativeName" => "Frysk" + ], + "xh" => [ + "name" => "Xhosa", + "nativeName" => "isiXhosa" + ], + "yi" => [ + "name" => "Yiddish", + "nativeName" => "ייִדיש" + ], + "yo" => [ + "name" => "Yoruba", + "nativeName" => "Yorùbá" + ], + "za" => [ + "name" => "Zhuang", + "nativeName" => "Saɯ cueŋƅ" + ] + ]; + + public static function getName($code) + { + return static::get($code, 'name'); + } + + public static function getNativeName($code) + { + if (strlen($code) == 2) { + return static::get($code, 'nativeName'); + } else { + return static::get(substr($code, 0, 2), 'nativeName') . ' (' . substr($code, -2) . ')'; + } + } + + public static function getNames(array $keys) + { + $results = []; + foreach ($keys as $key) { + if (isset(static::$codes[$key])) { + $results[$key] = static::$codes[$key]; + } + + } + return $results; + } + + protected static function get($code, $type) + { + if (isset(static::$codes[$code][$type])) { + return static::$codes[$code][$type]; + } else { + return false; + } + } +} diff --git a/system/src/Grav/Common/Markdown/Parsedown.php b/system/src/Grav/Common/Markdown/Parsedown.php new file mode 100644 index 0000000..95ead42 --- /dev/null +++ b/system/src/Grav/Common/Markdown/Parsedown.php @@ -0,0 +1,23 @@ +init($page, $defaults); + } + +} diff --git a/system/src/Grav/Common/Markdown/ParsedownExtra.php b/system/src/Grav/Common/Markdown/ParsedownExtra.php new file mode 100644 index 0000000..515d1dc --- /dev/null +++ b/system/src/Grav/Common/Markdown/ParsedownExtra.php @@ -0,0 +1,23 @@ +init($page, $defaults); + } +} diff --git a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php new file mode 100644 index 0000000..2c06104 --- /dev/null +++ b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php @@ -0,0 +1,360 @@ +page = $page; + $this->pages = $grav['pages']; + $this->uri = $grav['uri']; + $this->BlockTypes['{'] [] = "TwigTag"; + $this->pages_dir = self::getGrav()['locator']->findResource('page://'); + $this->special_chars = ['>' => 'gt', '<' => 'lt', '"' => 'quot']; + + if ($defaults === null) { + $defaults = self::getGrav()['config']->get('system.pages.markdown'); + } + + $this->setBreaksEnabled($defaults['auto_line_breaks']); + $this->setUrlsLinked($defaults['auto_url_links']); + $this->setMarkupEscaped($defaults['escape_markup']); + $this->setSpecialChars($defaults['special_chars']); + + $grav->fireEvent('onMarkdownInitialized', new Event(['markdown' => $this])); + + } + + /** + * Be able to define a new Block type or override an existing one + * + * @param $type + * @param $tag + */ + public function addBlockType($type, $tag, $continuable = false, $completable = false) + { + $this->BlockTypes[$type] [] = $tag; + + if ($continuable) { + $this->continuable_blocks[] = $tag; + } + + if ($completable) { + $this->completable_blocks[] = $tag; + } + } + + /** + * Be able to define a new Inline type or override an existing one + * + * @param $type + * @param $tag + */ + public function addInlineType($type, $tag) + { + $this->InlineTypes[$type] [] = $tag; + $this->inlineMarkerList .= $type; + } + + /** + * Overrides the default behavior to allow for plugin-provided blocks to be continuable + * + * @param $Type + * + * @return bool + */ + protected function isBlockContinuable($Type) + { + $continuable = in_array($Type, $this->continuable_blocks) || method_exists($this, 'block' . $Type . 'Continue'); + + return $continuable; + } + + /** + * Overrides the default behavior to allow for plugin-provided blocks to be completable + * + * @param $Type + * + * @return bool + */ + protected function isBlockCompletable($Type) + { + $completable = in_array($Type, $this->completable_blocks) || method_exists($this, 'block' . $Type . 'Complete'); + + return $completable; + } + + + /** + * Make the element function publicly accessible, Medium uses this to render from Twig + * + * @param array $Element + * + * @return string markup + */ + public function elementToHtml(array $Element) + { + return $this->element($Element); + } + + /** + * Setter for special chars + * + * @param $special_chars + * + * @return $this + */ + function setSpecialChars($special_chars) + { + $this->special_chars = $special_chars; + + return $this; + } + + /** + * Ensure Twig tags are treated as block level items with no

tags + */ + protected function blockTwigTag($Line) + { + if (preg_match('/(?:{{|{%|{#)(.*)(?:}}|%}|#})/', $Line['body'], $matches)) { + $Block = [ + 'markup' => $Line['body'], + ]; + + return $Block; + } + } + + protected function inlineSpecialCharacter($Excerpt) + { + if ($Excerpt['text'][0] === '&' && !preg_match('/^&#?\w+;/', $Excerpt['text'])) { + return [ + 'markup' => '&', + 'extent' => 1, + ]; + } + + if (isset($this->special_chars[$Excerpt['text'][0]])) { + return [ + 'markup' => '&' . $this->special_chars[$Excerpt['text'][0]] . ';', + 'extent' => 1, + ]; + } + } + + protected function inlineImage($excerpt) + { + if (preg_match($this->twig_link_regex, $excerpt['text'], $matches)) { + $excerpt['text'] = str_replace($matches[1], '/', $excerpt['text']); + $excerpt = parent::inlineImage($excerpt); + $excerpt['element']['attributes']['src'] = $matches[1]; + $excerpt['extent'] = $excerpt['extent'] + strlen($matches[1]) - 1; + + return $excerpt; + } else { + $excerpt['type'] = 'image'; + $excerpt = parent::inlineImage($excerpt); + } + + // Some stuff we will need + $actions = []; + $media = null; + + // if this is an image + if (isset($excerpt['element']['attributes']['src'])) { + $alt = $excerpt['element']['attributes']['alt'] ?: ''; + $title = $excerpt['element']['attributes']['title'] ?: ''; + $class = isset($excerpt['element']['attributes']['class']) ? $excerpt['element']['attributes']['class'] : ''; + + //get the url and parse it + $url = parse_url(htmlspecialchars_decode($excerpt['element']['attributes']['src'])); + + $this_host = isset($url['host']) && $url['host'] == $this->uri->host(); + + // if there is no host set but there is a path, the file is local + if ((!isset($url['host']) || $this_host) && isset($url['path'])) { + $path_parts = pathinfo($url['path']); + + // get the local path to page media if possible + if ($path_parts['dirname'] == $this->page->url(false, false, false)) { + // get the media objects for this page + $media = $this->page->media(); + } else { + // see if this is an external page to this one + $base_url = rtrim(self::getGrav()['base_url_relative'] . self::getGrav()['pages']->base(), '/'); + $page_route = '/' . ltrim(str_replace($base_url, '', $path_parts['dirname']), '/'); + + $ext_page = $this->pages->dispatch($page_route, true); + if ($ext_page) { + $media = $ext_page->media(); + } + } + + // if there is a media file that matches the path referenced.. + if ($media && isset($media->all()[$path_parts['basename']])) { + // get the medium object + $medium = $media->all()[$path_parts['basename']]; + + // 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[] = ['method' => $parts[0], 'params' => $value]; + + return $carry; + }, []); + } + + // loop through actions for the image and call them + foreach ($actions as $action) { + $medium = call_user_func_array([$medium, $action['method']], + explode(',', urldecode($action['params']))); + } + + if (isset($url['fragment'])) { + $medium->urlHash($url['fragment']); + } + + $excerpt['element'] = $medium->parseDownElement($title, $alt, $class, true); + + } else { + // not a current page media file, see if it needs converting to relative + $excerpt['element']['attributes']['src'] = Uri::buildUrl($url); + } + } + } + + return $excerpt; + } + + protected function inlineLink($excerpt) + { + if (isset($excerpt['type'])) { + $type = $excerpt['type']; + } else { + $type = 'link'; + } + + // do some trickery to get around Parsedown requirement for valid URL if its Twig in there + if (preg_match($this->twig_link_regex, $excerpt['text'], $matches)) { + $excerpt['text'] = str_replace($matches[1], '/', $excerpt['text']); + $excerpt = parent::inlineLink($excerpt); + $excerpt['element']['attributes']['href'] = $matches[1]; + $excerpt['extent'] = $excerpt['extent'] + strlen($matches[1]) - 1; + + return $excerpt; + } else { + $excerpt = parent::inlineLink($excerpt); + } + + // if this is a link + 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] : true; + $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] = str_replace(',', ' ', $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']); + } + + // set path to / if not set + if (empty($url['path'])) { + $url['path'] = ''; + } + + // if special scheme, just return + if(isset($url['scheme']) && in_array($url['scheme'], $this->special_protocols)) { + return $excerpt; + } + + // handle paths and such + $url = Uri::convertUrl($this->page, $url, $type); + + // build the URL from the component parts and set it on the element + $excerpt['element']['attributes']['href'] = Uri::buildUrl($url); + } + + return $excerpt; + } + + // For extending this class via plugins + public function __call($method, $args) + { + if (isset($this->$method) === true) { + $func = $this->$method; + + return call_user_func_array($func, $args); + } + } +} diff --git a/system/src/Grav/Common/Page/Collection.php b/system/src/Grav/Common/Page/Collection.php new file mode 100644 index 0000000..861f55b --- /dev/null +++ b/system/src/Grav/Common/Page/Collection.php @@ -0,0 +1,549 @@ +params = $params; + $this->pages = $pages ? $pages : Grav::instance()->offsetGet('pages'); + } + + /** + * Get the collection params + * + * @return array + */ + public function params() + { + return $this->params; + } + + /** + * Add a single page to a collection + * + * @param Page $page + * + * @return $this + */ + public function addPage(Page $page) + { + $this->items[$page->path()] = ['slug' => $page->slug()]; + + return $this; + } + + /** + * + * Create a copy of this collection + * + * @return static + */ + public function copy() + { + return new static($this->items, $this->params, $this->pages); + } + + /** + * Set parameters to the Collection + * + * @param array $params + * + * @return $this + */ + public function setParams(array $params) + { + $this->params = array_merge($this->params, $params); + + return $this; + } + + /** + * Returns current page. + * + * @return Page + */ + public function current() + { + $current = parent::key(); + + return $this->pages->get($current); + } + + /** + * Returns current slug. + * + * @return mixed + */ + public function key() + { + $current = parent::current(); + + return $current['slug']; + } + + /** + * Returns the value at specified offset. + * + * @param mixed $offset The offset to retrieve. + * + * @return mixed Can return all value types. + */ + public function offsetGet($offset) + { + return !empty($this->items[$offset]) ? $this->pages->get($offset) : null; + } + + /** + * Remove item from the list. + * + * @param Page|string|null $key + * + * @return $this|void + * @throws \InvalidArgumentException + */ + public function remove($key = null) + { + if ($key instanceof Page) { + $key = $key->path(); + } elseif (is_null($key)) { + $key = key($this->items); + } + if (!is_string($key)) { + throw new \InvalidArgumentException('Invalid argument $key.'); + } + + parent::remove($key); + + return $this; + } + + /** + * Reorder collection. + * + * @param string $by + * @param string $dir + * @param array $manual + * + * @return $this + */ + public function order($by, $dir = 'asc', $manual = null) + { + $this->items = $this->pages->sortCollection($this, $by, $dir, $manual); + + return $this; + } + + /** + * Check to see if this item is the first in the collection. + * + * @param string $path + * + * @return boolean True if item is first. + */ + public function isFirst($path) + { + if ($this->items && $path == array_keys($this->items)[0]) { + return true; + } else { + return false; + } + } + + /** + * Check to see if this item is the last in the collection. + * + * @param string $path + * + * @return boolean True if item is last. + */ + public function isLast($path) + { + if ($this->items && $path == array_keys($this->items)[count($this->items) - 1]) { + return true; + } else { + return false; + } + } + + /** + * Gets the previous sibling based on current position. + * + * @param string $path + * + * @return Page The previous item. + */ + public function prevSibling($path) + { + return $this->adjacentSibling($path, -1); + } + + /** + * Gets the next sibling based on current position. + * + * @param string $path + * + * @return Page The next item. + */ + public function nextSibling($path) + { + return $this->adjacentSibling($path, 1); + } + + /** + * Returns the adjacent sibling based on a direction. + * + * @param string $path + * @param integer $direction either -1 or +1 + * + * @return Page The sibling item. + */ + public function adjacentSibling($path, $direction = 1) + { + $values = array_keys($this->items); + $keys = array_flip($values); + + if (array_key_exists($path, $keys)) { + $index = $keys[$path] - $direction; + + return isset($values[$index]) ? $this->offsetGet($values[$index]) : $this; + } + + return $this; + + } + + /** + * Returns the item in the current position. + * + * @param string $path the path the item + * + * @return Page Item in the array the the current position. + */ + public function currentPosition($path) + { + return array_search($path, array_keys($this->items)); + } + + /** + * Returns the items between a set of date ranges of either the page date field (default) or + * an arbitrary datetime page field where end date is optional + * Dates can be passed in as text that strtotime() can process + * http://php.net/manual/en/function.strtotime.php + * + * @param $startDate + * @param bool $endDate + * @param $field + * + * @return $this + * @throws \Exception + */ + public function dateRange($startDate, $endDate = false, $field = false) + { + $start = Utils::date2timestamp($startDate); + $end = $endDate ? Utils::date2timestamp($endDate) : strtotime("now +1000 years"); + + $date_range = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + if ($page !== null) { + $date = $field ? strtotime($page->value($field)) : $page->date(); + + if ($date > $start && $date < $end) { + $date_range[$path] = $slug; + } + } + } + $this->items = $date_range; + + return $this; + } + + /** + * Creates new collection with only visible pages + * + * @return Collection The collection with only visible pages + */ + public function visible() + { + $visible = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + if ($page !== null && $page->visible()) { + $visible[$path] = $slug; + } + } + $this->items = $visible; + + return $this; + } + + /** + * Creates new collection with only non-visible pages + * + * @return Collection The collection with only non-visible pages + */ + public function nonVisible() + { + $visible = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + if ($page !== null && !$page->visible()) { + $visible[$path] = $slug; + } + } + $this->items = $visible; + + return $this; + } + + /** + * Creates new collection with only modular pages + * + * @return Collection The collection with only modular pages + */ + public function modular() + { + $modular = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + if ($page !== null && $page->modular()) { + $modular[$path] = $slug; + } + } + $this->items = $modular; + + return $this; + } + + /** + * Creates new collection with only non-modular pages + * + * @return Collection The collection with only non-modular pages + */ + public function nonModular() + { + $modular = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + if ($page !== null && !$page->modular()) { + $modular[$path] = $slug; + } + } + $this->items = $modular; + + return $this; + } + + /** + * Creates new collection with only published pages + * + * @return Collection The collection with only published pages + */ + public function published() + { + $published = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + if ($page !== null && $page->published()) { + $published[$path] = $slug; + } + } + $this->items = $published; + + return $this; + } + + /** + * Creates new collection with only non-published pages + * + * @return Collection The collection with only non-published pages + */ + public function nonPublished() + { + $published = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + if ($page !== null && !$page->published()) { + $published[$path] = $slug; + } + } + $this->items = $published; + + return $this; + } + + /** + * Creates new collection with only routable pages + * + * @return Collection The collection with only routable pages + */ + public function routable() + { + $routable = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + + if ($page !== null && $page->routable()) { + $routable[$path] = $slug; + } + } + + $this->items = $routable; + + return $this; + } + + /** + * Creates new collection with only non-routable pages + * + * @return Collection The collection with only non-routable pages + */ + public function nonRoutable() + { + $routable = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + if ($page !== null && !$page->routable()) { + $routable[$path] = $slug; + } + } + $this->items = $routable; + + return $this; + } + + /** + * Creates new collection with only pages of the specified type + * + * @param $type + * + * @return Collection The collection + */ + public function ofType($type) + { + $items = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + if ($page !== null && $page->template() == $type) { + $items[$path] = $slug; + } + } + + $this->items = $items; + + return $this; + } + + /** + * Creates new collection with only pages of one of the specified types + * + * @param $types + * + * @return Collection The collection + */ + public function ofOneOfTheseTypes($types) + { + $items = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + if ($page !== null && in_array($page->template(), $types)) { + $items[$path] = $slug; + } + } + + $this->items = $items; + + return $this; + } + + /** + * Creates new collection with only pages of one of the specified access levels + * + * @param $accessLevels + * + * @return Collection The collection + */ + public function ofOneOfTheseAccessLevels($accessLevels) + { + $items = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + + if ($page !== null && isset($page->header()->access)) { + if (is_array($page->header()->access)) { + //Multiple values for access + $valid = false; + + foreach ($page->header()->access as $index => $accessLevel) { + if (is_array($accessLevel)) { + foreach ($accessLevel as $innerIndex => $innerAccessLevel) { + if (in_array($innerAccessLevel, $accessLevels)) { + $valid = true; + } + } + } else { + if (in_array($index, $accessLevels)) { + $valid = true; + } + } + } + if ($valid) { + $items[$path] = $slug; + } + } else { + //Single value for access + if (in_array($page->header()->access, $accessLevels)) { + $items[$path] = $slug; + } + } + + } + } + + $this->items = $items; + + return $this; + } +} diff --git a/system/src/Grav/Common/Page/Header.php b/system/src/Grav/Common/Page/Header.php new file mode 100644 index 0000000..ad59c26 --- /dev/null +++ b/system/src/Grav/Common/Page/Header.php @@ -0,0 +1,15 @@ +path = $path; + + $iterator = new \FilesystemIterator($path, \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS); + + $media = []; + + /** @var \DirectoryIterator $info */ + foreach ($iterator as $path => $info) { + // Ignore folders and Markdown files. + if (!$info->isFile() || $info->getExtension() == 'md' || $info->getBasename() === '.DS_Store') { + continue; + } + + // Find out what type we're dealing with + list($basename, $ext, $type, $extra) = $this->getFileParts($info->getFilename()); + + $media["{$basename}.{$ext}"] = isset($media["{$basename}.{$ext}"]) ? $media["{$basename}.{$ext}"] : []; + + if ($type === 'alternative') { + $media["{$basename}.{$ext}"][$type] = isset($media["{$basename}.{$ext}"][$type]) ? $media["{$basename}.{$ext}"][$type] : []; + $media["{$basename}.{$ext}"][$type][$extra] = [ 'file' => $path, 'size' => $info->getSize() ]; + } else { + $media["{$basename}.{$ext}"][$type] = [ 'file' => $path, 'size' => $info->getSize() ]; + } + } + + foreach ($media as $name => $types) { + // First prepare the alternatives in case there is no base medium + if (!empty($types['alternative'])) { + foreach ($types['alternative'] as $ratio => &$alt) { + $alt['file'] = MediumFactory::fromFile($alt['file']); + + if (!$alt['file']) { + unset($types['alternative'][$ratio]); + } else { + $alt['file']->set('size', $alt['size']); + } + } + } + + // Create the base medium + if (!empty($types['base'])) { + $medium = MediumFactory::fromFile($types['base']['file']); + $medium && $medium->set('size', $types['base']['size']); + } else if (!empty($types['alternative'])) { + $altMedium = reset($types['alternative']); + $ratio = key($types['alternative']); + + $altMedium = $altMedium['file']; + + $medium = MediumFactory::scaledFromMedium($altMedium, $ratio, 1)['file']; + } + + if (!$medium) { + continue; + } + + if (!empty($types['meta'])) { + $medium->addMetaFile($types['meta']['file']); + } + + if (!empty($types['thumb'])) { + // We will not turn it into medium yet because user might never request the thumbnail + // not wasting any resources on that, maybe we should do this for medium in general? + $medium->set('thumbnails.page', $types['thumb']['file']); + } + + // Build missing alternatives + if (!empty($types['alternative'])) { + $alternatives = $types['alternative']; + + $max = max(array_keys($alternatives)); + + for ($i=2; $i < $max; $i++) { + if (isset($alternatives[$i])) { + continue; + } + + $types['alternative'][$i] = MediumFactory::scaledFromMedium($alternatives[$max]['file'], $max, $i); + } + + foreach ($types['alternative'] as $ratio => $altMedium) { + $medium->addAlternative($ratio, $altMedium['file']); + } + } + + $this->add($name, $medium); + } + } + + /** + * Get medium by filename. + * + * @param string $filename + * @return Medium|null + */ + public function get($filename) + { + return isset($this->instances[$filename]) ? $this->instances[$filename] : null; + } + + /** + * Get a list of all media. + * + * @return array|Medium[] + */ + public function all() + { + ksort($this->instances, SORT_NATURAL | SORT_FLAG_CASE); + return $this->instances; + } + + /** + * Get a list of all image media. + * + * @return array|Medium[] + */ + public function images() + { + ksort($this->images, SORT_NATURAL | SORT_FLAG_CASE); + return $this->images; + } + + /** + * Get a list of all video media. + * + * @return array|Medium[] + */ + public function videos() + { + ksort($this->videos, SORT_NATURAL | SORT_FLAG_CASE); + return $this->videos; + } + + /** + * Get a list of all audio media. + * + * @return array|Medium[] + */ + public function audios() + { + ksort($this->audios, SORT_NATURAL | SORT_FLAG_CASE); + return $this->audios; + } + + /** + * Get a list of all file media. + * + * @return array|Medium[] + */ + public function files() + { + ksort($this->files, SORT_NATURAL | SORT_FLAG_CASE); + return $this->files; + } + + /** + * @internal + */ + protected function add($name, $file) + { + $this->instances[$name] = $file; + switch ($file->type) { + case 'image': + $this->images[$name] = $file; + break; + case 'video': + $this->videos[$name] = $file; + break; + case 'audio': + $this->audios[$name] = $file; + break; + default: + $this->files[$name] = $file; + } + } + + /** + * Get filename, extension and meta part. + * + * @param string $filename + * @return array + */ + protected function getFileParts($filename) + { + $fileParts = explode('.', $filename); + + $name = array_shift($fileParts); + $type = 'base'; + $extra = null; + + if (preg_match('/(.*)@(\d+)x\.(.*)$/', $filename, $matches)) { + $name = $matches[1]; + $extension = $matches[3]; + $extra = (int) $matches[2]; + $type = 'alternative'; + + if ($extra === 1) { + $type = 'base'; + $extra = null; + } + } else { + $extension = null; + while (($part = array_shift($fileParts)) !== null) { + if ($part != 'meta' && $part != 'thumb') { + if (isset($extension)) { + $name .= '.' . $extension; + } + $extension = $part; + } else { + $type = $part; + $extra = '.' . $part . '.' . implode('.', $fileParts); + break; + } + } + } + + return array($name, $extension, $type, $extra); + } +} diff --git a/system/src/Grav/Common/Page/Medium/AudioMedium.php b/system/src/Grav/Common/Page/Medium/AudioMedium.php new file mode 100644 index 0000000..952b061 --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/AudioMedium.php @@ -0,0 +1,38 @@ +url($reset); + + return [ + 'name' => 'audio', + 'text' => 'Your browser does not support the audio tag.', + 'attributes' => $attributes + ]; + } + + /** + * Reset medium. + * + * @return $this + */ + public function reset() + { + parent::reset(); + + $this->attributes['controls'] = true; + return $this; + } +} diff --git a/system/src/Grav/Common/Page/Medium/ImageFile.php b/system/src/Grav/Common/Page/Medium/ImageFile.php new file mode 100644 index 0000000..c71cd0e --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/ImageFile.php @@ -0,0 +1,96 @@ +operations = []; + } + + /** + * This is the same as the Gregwar Image class except this one fires a Grav Event on creation of new cached file + * + * @param string $type the image type + * @param int $quality the quality (for JPEG) + * @param bool $actual + * + * @return mixed|string + */ + public function cacheFile($type = 'jpg', $quality = 80, $actual = false) + { + if ($type == 'guess') { + $type = $this->guessType(); + } + + if (!count($this->operations) && $type == $this->guessType() && !$this->forceCache) { + return $this->getFilename($this->getFilePath()); + } + + // Computes the hash + $this->hash = $this->getHash($type, $quality); + + // Generates the cache file + $cacheFile = ''; + + if (!$this->prettyName || $this->prettyPrefix) { + $cacheFile .= $this->hash; + } + + if ($this->prettyPrefix) { + $cacheFile .= '-'; + } + + if ($this->prettyName) { + $cacheFile .= $this->prettyName; + } + + + $cacheFile .= '.'.$type; + + // If the files does not exists, save it + $image = $this; + + // Target file should be younger than all the current image + // dependencies + $conditions = array( + 'younger-than' => $this->getDependencies() + ); + + // The generating function + $generate = function ($target) use ($image, $type, $quality) { + $result = $image->save($target, $type, $quality); + + if ($result != $target) { + throw new GenerationError($result); + } + + self::getGrav()->fireEvent('onImageMediumSaved', new Event(['image' => $target])); + }; + + // Asking the cache for the cacheFile + try { + $perms = self::getGrav()['config']->get('system.images.cache_perms', '0755'); + $perms = octdec($perms); + $file = $this->cache->setDirectoryMode($perms)->getOrCreateFile($cacheFile, $conditions, $generate, $actual); + } catch (GenerationError $e) { + $file = $e->getNewFile(); + } + + if ($actual) { + return $file; + } else { + return $this->getFilename($file); + } + } +} diff --git a/system/src/Grav/Common/Page/Medium/ImageMedium.php b/system/src/Grav/Common/Page/Medium/ImageMedium.php new file mode 100644 index 0000000..4766908 --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/ImageMedium.php @@ -0,0 +1,555 @@ + [ 0, 1 ], + 'forceResize' => [ 0, 1 ], + 'cropResize' => [ 0, 1 ], + 'crop' => [ 0, 1, 2, 3 ], + 'zoomCrop' => [ 0, 1 ] + ]; + + /** + * @var array + */ + protected $derivatives = []; + + /** + * @var string + */ + protected $sizes = '100vw'; + + /** + * Construct. + * + * @param array $items + * @param Blueprint $blueprint + */ + public function __construct($items = [], Blueprint $blueprint = null) + { + parent::__construct($items, $blueprint); + + $config = self::$grav['config']; + + $image_info = getimagesize($this->get('filepath')); + $this->def('width', $image_info[0]); + $this->def('height', $image_info[1]); + $this->def('mime', $image_info['mime']); + $this->def('debug', $config->get('system.images.debug')); + + $this->set('thumbnails.media', $this->get('filepath')); + + $this->default_quality = $config->get('system.images.default_image_quality', 85); + + $this->reset(); + + if ($config->get('system.images.cache_all', false)) { + $this->cache(); + } + } + + /** + * Add meta file for the medium. + * + * @param $filepath + * @return $this + */ + public function addMetaFile($filepath) + { + parent::addMetaFile($filepath); + + // Apply filters in meta file + $this->reset(); + + return $this; + } + + /** + * Return PATH to image. + * + * @param bool $reset + * @return string path to image + */ + public function path($reset = true) + { + $output = $this->saveImage(); + + if ($reset) { + $this->reset(); + } + + return $output; + } + + /** + * Return URL to image. + * + * @param bool $reset + * @return string + */ + public function url($reset = true) + { + $image_path = self::$grav['locator']->findResource('cache://images', true); + $image_dir = self::$grav['locator']->findResource('cache://images', false); + $saved_image_path = $this->saveImage(); + + $output = preg_replace('|^' . preg_quote(GRAV_ROOT) . '|', '', $saved_image_path); + + if (Utils::startsWith($output, $image_path)) { + $output = '/' . $image_dir . preg_replace('|^' . preg_quote($image_path) . '|', '', $output); + } + + if ($reset) { + $this->reset(); + } + + return self::$grav['base_url'] . $output . $this->querystring() . $this->urlHash(); + } + + /** + * Simply processes with no extra methods. Useful for triggering events. + * + * @return $this + */ + public function cache() + { + if (!$this->image) { + $this->image(); + } + + return $this; + } + + + /** + * Return srcset string for this Medium and its alternatives. + * + * @param bool $reset + * @return string + */ + public function srcset($reset = true) + { + if (empty($this->alternatives) && empty($this->derivatives)) { + if ($reset) { + $this->reset(); + } + return ''; + } + + if (!empty($this->derivatives)) { + asort($this->derivatives); + + foreach ($this->derivatives as $url => $width) { + $srcset[] = $url . ' ' . $width . 'w'; + } + + $srcset[] = $this->url($reset) . ' ' . $this->get('width') . 'w'; + } + else { + $srcset = [ $this->url($reset) . ' ' . $this->get('width') . 'w' ]; + foreach ($this->alternatives as $ratio => $medium) { + $srcset[] = $medium->url($reset) . ' ' . $medium->get('width') . 'w'; + } + } + + return implode(', ', $srcset); + } + + /** + * Generate derivatives + * + * @param int $min_width + * @param int $max_width + * @param int $step + * @return $this + */ + public function derivatives($min_width, $max_width, $step = 200) { + $width = $min_width; + + // Do not upscale images. + if ($max_width > $this->get('width')) { + $max_width = $this->get('width'); + } + + while ($width <= $max_width) { + $ratio = $width / $this->get('width'); + $derivative = MediumFactory::scaledFromMedium($this, 1, $ratio); + if (is_array($derivative)) { + $this->addDerivative($derivative['file']); + } + $width += $step; + } + return $this; + } + + /** + * Add a derivative + * + * @param ImageMedium $image + */ + public function addDerivative(ImageMedium $image) { + $this->derivatives[$image->url()] = $image->get('width'); + } + + /** + * Parsedown element for source display mode + * + * @param array $attributes + * @param boolean $reset + * @return array + */ + public function sourceParsedownElement(array $attributes, $reset = true) + { + empty($attributes['src']) && $attributes['src'] = $this->url(false); + + $srcset = $this->srcset($reset); + if ($srcset) { + empty($attributes['srcset']) && $attributes['srcset'] = $srcset; + $attributes['sizes'] = $this->sizes(); + } + + return [ 'name' => 'img', 'attributes' => $attributes ]; + } + + /** + * Reset image. + * + * @return $this + */ + public function reset() + { + parent::reset(); + + if ($this->image) { + $this->image(); + $this->image->clearOperations(); // Clear previously applied operations + $this->querystring(''); + $this->filter(); + } + + $this->format = 'guess'; + $this->quality = $this->default_quality; + + $this->debug_watermarked = false; + + return $this; + } + + /** + * Turn the current Medium into a Link + * + * @param boolean $reset + * @param array $attributes + * @return Link + */ + public function link($reset = true, array $attributes = []) + { + $attributes['href'] = $this->url(false); + $srcset = $this->srcset(false); + if ($srcset) { + $attributes['data-srcset'] = $srcset; + } + + return parent::link($reset, $attributes); + } + + /** + * Turn the current Medium into a Link with lightbox enabled + * + * @param int $width + * @param int $height + * @param boolean $reset + * @return Link + */ + public function lightbox($width = null, $height = null, $reset = true) + { + if ($this->mode !== 'source') { + $this->display('source'); + } + + if ($width && $height) { + $this->cropResize($width, $height); + } + + return parent::lightbox($width, $height, $reset); + } + + /** + * Sets or gets the quality of the image + * + * @param int $quality 0-100 quality + * @return Medium + */ + public function quality($quality = null) + { + if ($quality) { + if (!$this->image) { + $this->image(); + } + + $this->quality = $quality; + return $this; + } + + return $this->quality; + } + + /** + * Sets image output format. + * + * @param string $format + * @return $this + */ + public function format($format) + { + if (!$this->image) { + $this->image(); + } + + $this->format = $format; + return $this; + } + + /** + * Set or get sizes parameter for srcset media action + * + * @param string $sizes + * @return $this + */ + public function sizes($sizes = null) + { + + if ($sizes) { + $this->sizes = $sizes; + return $this; + } + + return empty($this->sizes) ? '100vw' : $this->sizes; + } + + /** + * Allows to set the width attribute from Markdown or Twig + * Examples: ![Example](myimg.png?width=200&height=400) + * ![Example](myimg.png?resize=100,200&width=100&height=200) + * ![Example](myimg.png?width=auto&height=auto) + * ![Example](myimg.png?width&height) + * {{ page.media['myimg.png'].width().height().html }} + * {{ page.media['myimg.png'].resize(100,200).width(100).height(200).html }} + * + * @param mixed $value A value or 'auto' or empty to use the width of the image + * @return $this + */ + public function width($value = 'auto') + { + if (!$value || $value == 'auto') + $this->attributes['width'] = $this->get('width'); + else + $this->attributes['width'] = $value; + return $this; + } + + /** + * Allows to set the height attribute from Markdown or Twig + * Examples: ![Example](myimg.png?width=200&height=400) + * ![Example](myimg.png?resize=100,200&width=100&height=200) + * ![Example](myimg.png?width=auto&height=auto) + * ![Example](myimg.png?width&height) + * {{ page.media['myimg.png'].width().height().html }} + * {{ page.media['myimg.png'].resize(100,200).width(100).height(200).html }} + * + * @param mixed $value A value or 'auto' or empty to use the height of the image + * @return $this + */ + public function height($value = 'auto') + { + if (!$value || $value == 'auto') + $this->attributes['height'] = $this->get('height'); + else + $this->attributes['height'] = $value; + return $this; + } + + /** + * Forward the call to the image processing method. + * + * @param string $method + * @param mixed $args + * @return $this|mixed + */ + public function __call($method, $args) + { + if ($method == 'cropZoom') { + $method = 'zoomCrop'; + } + + if (!in_array($method, self::$magic_actions)) { + return parent::__call($method, $args); + } + + // Always initialize image. + if (!$this->image) { + $this->image(); + } + + try { + call_user_func_array([$this->image, $method], $args); + + foreach ($this->alternatives as $ratio => $medium) { + $args_copy = $args; + + // regular image: resize 400x400 -> 200x200 + // --> @2x: resize 800x800->400x400 + if (isset(self::$magic_resize_actions[$method])) { + foreach (self::$magic_resize_actions[$method] as $param) { + if (isset($args_copy[$param])) { + $args_copy[$param] = (int) $args_copy[$param] * $ratio; + } + } + } + + call_user_func_array([$medium, $method], $args_copy); + } + } catch (\BadFunctionCallException $e) { + } + + return $this; + } + + /** + * Gets medium image, resets image manipulation operations. + * + * @return $this + */ + protected function image() + { + $locator = self::$grav['locator']; + + $file = $this->get('filepath'); + $cacheDir = $locator->findResource('cache://images', true); + + $this->image = ImageFile::open($file) + ->setCacheDir($cacheDir) + ->setActualCacheDir($cacheDir) + ->setPrettyName(basename($this->get('basename'))); + + $this->filter(); + + return $this; + } + + /** + * Save the image with cache. + * + * @return mixed|string + */ + protected function saveImage() + { + if (!$this->image) { + return parent::path(false); + } + + if (isset($this->result)) { + return $this->result; + } + + if ($this->get('debug') && !$this->debug_watermarked) { + $ratio = $this->get('ratio'); + if (!$ratio) { + $ratio = 1; + } + + $locator = self::$grav['locator']; + $overlay = $locator->findResource("system://assets/responsive-overlays/{$ratio}x.png") ?: $locator->findResource('system://assets/responsive-overlays/unknown.png'); + $this->image->merge(ImageFile::open($overlay)); + } + + return $this->image->cacheFile($this->format, $this->quality); + } + + /** + * Filter image by using user defined filter parameters. + * + * @param string $filter Filter to be used. + */ + public function filter($filter = 'image.filters.default') + { + $filters = (array) $this->get($filter, []); + foreach ($filters as $params) { + $params = (array) $params; + $method = array_shift($params); + $this->__call($method, $params); + } + } + + /** + * Return the image higher quality version + * + * @return ImageMedium the alternative version with higher quality + */ + public function higherQualityAlternative() + { + if ($this->alternatives) { + $max = reset($this->alternatives); + foreach($this->alternatives as $alternative) + { + if($alternative->quality() > $max->quality()) + { + $max = $alternative; + } + } + + return $max; + } else { + return $this; + } + } + +} diff --git a/system/src/Grav/Common/Page/Medium/Link.php b/system/src/Grav/Common/Page/Medium/Link.php new file mode 100644 index 0000000..a92ec22 --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/Link.php @@ -0,0 +1,65 @@ +attributes = $attributes; + $this->source = $medium->reset()->thumbnail('auto')->display('thumbnail'); + $this->source->linked = true; + } + + /** + * Get an element (is array) that can be rendered by the Parsedown engine + * + * @param string $title + * @param string $alt + * @param string $class + * @param boolean $reset + * @return array + */ + public function parsedownElement($title = null, $alt = null, $class = null, $reset = true) + { + $innerElement = $this->source->parsedownElement($title, $alt, $class, $reset); + + return [ + 'name' => 'a', + 'attributes' => $this->attributes, + 'handler' => is_string($innerElement) ? 'line' : 'element', + 'text' => $innerElement + ]; + } + + /** + * Forward the call to the source element + * + * @param string $method + * @param mixed $args + * @return $this|mixed + */ + public function __call($method, $args) + { + $this->source = call_user_func_array(array($this->source, $method), $args); + + // Don't start nesting links, if user has multiple link calls in his + // actions, we will drop the previous links. + return $this->source instanceof Link ? $this->source : $this; + } +} diff --git a/system/src/Grav/Common/Page/Medium/Medium.php b/system/src/Grav/Common/Page/Medium/Medium.php new file mode 100644 index 0000000..33e734e --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/Medium.php @@ -0,0 +1,494 @@ +get('system.media.enable_media_timestamp', true)) { + $this->querystring('&' . self::getGrav()['cache']->getKey()); + } + + $this->def('mime', 'application/octet-stream'); + $this->reset(); + } + + /** + * Return just metadata from the Medium object + * + * @return $this + */ + public function meta() + { + return new Data($this->items); + } + + /** + * Add meta file for the medium. + * + * @param $filepath + */ + public function addMetaFile($filepath) + { + $this->merge(CompiledYamlFile::instance($filepath)->content()); + } + + /** + * Add alternative Medium to this Medium. + * + * @param $ratio + * @param Medium $alternative + */ + public function addAlternative($ratio, Medium $alternative) + { + if (!is_numeric($ratio) || $ratio === 0) { + return; + } + + $alternative->set('ratio', $ratio); + $this->alternatives[(float) $ratio] = $alternative; + } + + /** + * Return string representation of the object (html). + * + * @return string + */ + public function __toString() + { + return $this->html(); + } + + /** + * Return PATH to file. + * + * @param bool $reset + * @return string path to file + */ + public function path($reset = true) + { + if ($reset) { + $this->reset(); + } + + return $this->get('filepath'); + } + + /** + * Return URL to file. + * + * @param bool $reset + * @return string + */ + public function url($reset = true) + { + $output = preg_replace('|^' . preg_quote(GRAV_ROOT) . '|', '', $this->get('filepath')); + + if ($reset) { + $this->reset(); + } + + return self::$grav['base_url'] . $output . $this->querystring() . $this->urlHash(); + } + + /** + * Get/set querystring for the file's url + * + * @param string $hash + * @param boolean $withHash + * @return string + */ + public function querystring($querystring = null, $withQuestionmark = true) + { + if (!is_null($querystring)) { + $this->set('querystring', ltrim($querystring, '?&')); + + foreach ($this->alternatives as $alt) { + $alt->querystring($querystring, $withQuestionmark); + } + } + + $querystring = $this->get('querystring', ''); + + if ($withQuestionmark && !empty($querystring)) { + return '?' . $querystring; + } else { + return $querystring; + } + } + + /** + * Get/set hash for the file's url + * + * @param string $hash + * @param boolean $withHash + * @return string + */ + public function urlHash($hash = null, $withHash = true) + { + if ($hash) { + $this->set('urlHash', ltrim($hash, '#')); + } + + $hash = $this->get('urlHash', ''); + + if ($withHash && !empty($hash)) { + return '#' . $hash; + } else { + return $hash; + } + } + + /** + * Get an element (is array) that can be rendered by the Parsedown engine + * + * @param string $title + * @param string $alt + * @param string $class + * @param boolean $reset + * @return array + */ + public function parsedownElement($title = null, $alt = null, $class = null, $reset = true) + { + $attributes = $this->attributes; + + $style = ''; + foreach ($this->styleAttributes as $key => $value) { + if (is_numeric($key)) // Special case for inline style attributes, refer to style() method + $style .= $value; + else + $style .= $key . ': ' . $value . ';'; + } + if ($style) { + $attributes['style'] = $style; + } + + if (empty($attributes['title'])) { + if (!empty($title)) { + $attributes['title'] = $title; + } elseif (!empty($this->items['title'])) { + $attributes['title'] = $this->items['title']; + } + } + + if (empty($attributes['alt'])) { + if (!empty($alt)) { + $attributes['alt'] = $alt; + } elseif (!empty($this->items['alt'])) { + $attributes['alt'] = $this->items['alt']; + } + } + + if (empty($attributes['class'])) { + if (!empty($class)) { + $attributes['class'] = $class; + } elseif (!empty($this->items['class'])) { + $attributes['class'] = $this->items['class']; + } + } + + switch ($this->mode) { + case 'text': + $element = $this->textParsedownElement($attributes, false); + break; + case 'thumbnail': + $element = $this->getThumbnail()->sourceParsedownElement($attributes, false); + break; + case 'source': + $element = $this->sourceParsedownElement($attributes, false); + break; + } + + if ($reset) { + $this->reset(); + } + + $this->display('source'); + + return $element; + } + + /** + * Parsedown element for source display mode + * + * @param array $attributes + * @param boolean $reset + * @return array + */ + protected function sourceParsedownElement(array $attributes, $reset = true) + { + return $this->textParsedownElement($attributes, $reset); + } + + /** + * Parsedown element for text display mode + * + * @param array $attributes + * @param boolean $reset + * @return array + */ + protected function textParsedownElement(array $attributes, $reset = true) + { + $text = empty($attributes['title']) ? empty($attributes['alt']) ? $this->get('filename') : $attributes['alt'] : $attributes['title']; + + $element = [ + 'name' => 'p', + 'attributes' => $attributes, + 'text' => $text + ]; + + if ($reset) { + $this->reset(); + } + + return $element; + } + + /** + * Reset medium. + * + * @return $this + */ + public function reset() + { + $this->attributes = []; + return $this; + } + + /** + * Switch display mode. + * + * @param string $mode + * + * @return $this + */ + public function display($mode = 'source') + { + if ($this->mode === $mode) { + return $this; + } + + + $this->mode = $mode; + + return $mode === 'thumbnail' ? $this->getThumbnail()->reset() : $this->reset(); + } + + /** + * Switch thumbnail. + * + * @param string $type + * + * @return $this + */ + public function thumbnail($type = 'auto') + { + if ($type !== 'auto' && !in_array($type, $this->thumbnailTypes)) { + return $this; + } + + if ($this->thumbnailType !== $type) { + $this->_thumbnail = null; + } + + $this->thumbnailType = $type; + + return $this; + } + + /** + * Turn the current Medium into a Link + * + * @param boolean $reset + * @param array $attributes + * @return Link + */ + public function link($reset = true, array $attributes = []) + { + if ($this->mode !== 'source') { + $this->display('source'); + } + + foreach ($this->attributes as $key => $value) { + empty($attributes['data-' . $key]) && $attributes['data-' . $key] = $value; + } + + empty($attributes['href']) && $attributes['href'] = $this->url(); + + return new Link($attributes, $this); + } + + /** + * Turn the current Medium into a Link with lightbox enabled + * + * @param int $width + * @param int $height + * @param boolean $reset + * @return Link + */ + public function lightbox($width = null, $height = null, $reset = true) + { + $attributes = ['rel' => 'lightbox']; + + if ($width && $height) { + $attributes['data-width'] = $width; + $attributes['data-height'] = $height; + } + + return $this->link($reset, $attributes); + } + + /** + * Add a class to the element from Markdown or Twig + * Example: ![Example](myimg.png?classes=float-left) or ![Example](myimg.png?classes=myclass1,myclass2) + * + * @return $this + */ + public function classes() + { + $classes = func_get_args(); + if (!empty($classes)) { + $this->attributes['class'] = implode(',', (array)$classes); + } + + return $this; + } + + /** + * Add an id to the element from Markdown or Twig + * Example: ![Example](myimg.png?id=primary-img) + * + * @param $id + * @return $this + */ + public function id($id) + { + if (is_string($id)) { + $this->attributes['id'] = trim($id); + } + + return $this; + } + + /** + * Allows to add an inline style attribute from Markdown or Twig + * Example: ![Example](myimg.png?style=float:left) + * + * @param string $style + * @return $this + */ + public function style($style) + { + $this->styleAttributes[] = rtrim($style, ';') . ';'; + return $this; + } + + /** + * Allow any action to be called on this medium from twig or markdown + * + * @param string $method + * @param mixed $args + * @return $this + */ + public function __call($method, $args) + { + $qs = $method; + if (count($args) > 1 || (count($args) == 1 && !empty($args[0]))) { + $qs .= '=' . implode(',', array_map(function ($a) { return urlencode($a); }, $args)); + } + + if (!empty($qs)) { + $this->querystring($this->querystring(null, false) . '&' . $qs); + } + + return $this; + } + + /** + * Get the thumbnail Medium object + * + * @return ThumbnailImageMedium + */ + protected function getThumbnail() + { + if (!$this->_thumbnail) { + $types = $this->thumbnailTypes; + + if ($this->thumbnailType !== 'auto') { + array_unshift($types, $this->thumbnailType); + } + + foreach ($types as $type) { + $thumb = $this->get('thumbnails.' . $type, false); + + if ($thumb) { + $thumb = $thumb instanceof ThumbnailImageMedium ? $thumb : MediumFactory::fromFile($thumb, ['type' => 'thumbnail']); + $thumb->parent = $this; + } + + if ($thumb) { + $this->_thumbnail = $thumb; + break; + } + } + } + + return $this->_thumbnail; + } + +} diff --git a/system/src/Grav/Common/Page/Medium/MediumFactory.php b/system/src/Grav/Common/Page/Medium/MediumFactory.php new file mode 100644 index 0000000..aa88db7 --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/MediumFactory.php @@ -0,0 +1,148 @@ +get("media.".strtolower($ext)); + if (!$media_params) { + return null; + } + + $params += $media_params; + + // Add default settings for undefined variables. + $params += $config->get('media.defaults'); + $params += [ + 'type' => 'file', + 'thumb' => 'media/thumb.png', + 'mime' => 'application/octet-stream', + 'filepath' => $file, + 'filename' => $filename, + 'basename' => $basename, + 'extension' => $ext, + 'path' => $path, + 'modified' => filemtime($file), + 'thumbnails' => [] + ]; + + $locator = self::getGrav()['locator']; + + $lookup = $locator->findResources('image://'); + foreach ($lookup as $lookupPath) { + if (is_file($lookupPath . '/' . $params['thumb'])) { + $params['thumbnails']['default'] = $lookupPath . '/' . $params['thumb']; + break; + } + } + + return static::fromArray($params); + } + + /** + * Create Medium from array of parameters + * + * @param array $items + * @param Blueprint|null $blueprint + * @return Medium + */ + public static function fromArray(array $items = [], Blueprint $blueprint = null) + { + $type = isset($items['type']) ? $items['type'] : null; + + switch ($type) { + case 'image': + return new ImageMedium($items, $blueprint); + break; + case 'thumbnail': + return new ThumbnailImageMedium($items, $blueprint); + break; + case 'animated': + case 'vector': + return new StaticImageMedium($items, $blueprint); + break; + case 'video': + return new VideoMedium($items, $blueprint); + break; + case 'audio': + return new AudioMedium($items, $blueprint); + break; + default: + return new Medium($items, $blueprint); + break; + } + } + + /** + * Create a new ImageMedium by scaling another ImageMedium object. + * + * @param ImageMedium $medium + * @param int $from + * @param int $to + * @return Medium + */ + public static function scaledFromMedium($medium, $from, $to) + { + if (! $medium instanceof ImageMedium) { + return $medium; + } + + if ($to > $from) { + return $medium; + } + + $ratio = $to / $from; + $width = (int) ($medium->get('width') * $ratio); + $height = (int) ($medium->get('height') * $ratio); + + $basename = $medium->get('basename'); + $basename = str_replace('@'.$from.'x', '@'.$to.'x', $basename); + + $debug = $medium->get('debug'); + $medium->set('debug', false); + + $file = $medium->resize($width, $height)->path(); + + $medium->set('debug', $debug); + + $size = filesize($file); + + $medium = self::fromFile($file); + $medium->set('size', $size); + + return ['file' => $medium, 'size' => $size]; + } +} diff --git a/system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php b/system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php new file mode 100644 index 0000000..7052dea --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php @@ -0,0 +1,31 @@ +parsedownElement($title, $alt, $class, $reset); + + if (!$this->parsedown) { + $this->parsedown = new Parsedown(null, null); + } + + return $this->parsedown->elementToHtml($element); + } +} diff --git a/system/src/Grav/Common/Page/Medium/RenderableInterface.php b/system/src/Grav/Common/Page/Medium/RenderableInterface.php new file mode 100644 index 0000000..f1ebf0d --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/RenderableInterface.php @@ -0,0 +1,34 @@ +url($reset); + + return [ 'name' => 'image', 'attributes' => $attributes ]; + } +} diff --git a/system/src/Grav/Common/Page/Medium/StaticResizeTrait.php b/system/src/Grav/Common/Page/Medium/StaticResizeTrait.php new file mode 100644 index 0000000..df42dcf --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/StaticResizeTrait.php @@ -0,0 +1,20 @@ +styleAttributes['width'] = $width . 'px'; + $this->styleAttributes['height'] = $height . 'px'; + + return $this; + } +} diff --git a/system/src/Grav/Common/Page/Medium/ThumbnailImageMedium.php b/system/src/Grav/Common/Page/Medium/ThumbnailImageMedium.php new file mode 100644 index 0000000..da63522 --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/ThumbnailImageMedium.php @@ -0,0 +1,121 @@ +bubble('parsedownElement', [$title, $alt, $class, $reset]); + } + + /** + * Return HTML markup from the medium. + * + * @param string $title + * @param string $alt + * @param string $class + * @param bool $reset + * @return string + */ + public function html($title = null, $alt = null, $class = null, $reset = true) + { + return $this->bubble('html', [$title, $alt, $class, $reset]); + } + + /** + * Switch display mode. + * + * @param string $mode + * + * @return $this + */ + public function display($mode = 'source') + { + return $this->bubble('display', [$mode], false); + } + + /** + * Switch thumbnail. + * + * @param string $type + * + * @return $this + */ + public function thumbnail($type = 'auto') + { + $this->bubble('thumbnail', [$type], false); + return $this->bubble('getThumbnail', [], false); + } + + /** + * Turn the current Medium into a Link + * + * @param boolean $reset + * @param array $attributes + * @return Link + */ + public function link($reset = true, array $attributes = []) + { + return $this->bubble('link', [$reset, $attributes], false); + } + + /** + * Turn the current Medium into a Link with lightbox enabled + * + * @param int $width + * @param int $height + * @param boolean $reset + * @return Link + */ + public function lightbox($width = null, $height = null, $reset = true) + { + return $this->bubble('lightbox', [$width, $height, $reset], false); + } + + /** + * Bubble a function call up to either the superclass function or the parent Medium instance + * + * @param string $method + * @param array $arguments + * @param boolean $testLinked + * @return Medium + */ + protected function bubble($method, array $arguments = [], $testLinked = true) + { + if (!$testLinked || $this->linked) { + return $this->parent ? call_user_func_array(array($this->parent, $method), $arguments) : $this; + } else { + return call_user_func_array(array($this, 'parent::' . $method), $arguments); + } + } +} diff --git a/system/src/Grav/Common/Page/Medium/VideoMedium.php b/system/src/Grav/Common/Page/Medium/VideoMedium.php new file mode 100644 index 0000000..754777e --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/VideoMedium.php @@ -0,0 +1,38 @@ +url($reset); + + return [ + 'name' => 'video', + 'text' => 'Your browser does not support the video tag.', + 'attributes' => $attributes + ]; + } + + /** + * Reset medium. + * + * @return $this + */ + public function reset() + { + parent::reset(); + + $this->attributes['controls'] = true; + return $this; + } +} diff --git a/system/src/Grav/Common/Page/Page.php b/system/src/Grav/Common/Page/Page.php new file mode 100644 index 0000000..99ccd70 --- /dev/null +++ b/system/src/Grav/Common/Page/Page.php @@ -0,0 +1,2448 @@ +taxonomy = []; + $this->process = $config->get('system.pages.process'); + $this->published = true; + + return $this; + } + + /** + * Initializes the page instance variables based on a file + * + * @param \SplFileInfo $file The file information for the .md file that the page represents + * @param string $extension + * + * @return $this + */ + public function init(\SplFileInfo $file, $extension = null) + { + $config = self::getGrav()['config']; + + $this->hide_home_route = $config->get('system.home.hide_in_urls', false); + $this->home_route = $config->get('system.home.alias'); + $this->filePath($file->getPathName()); + $this->modified($file->getMTime()); + $this->id($this->modified() . md5($this->filePath())); + $this->routable(true); + $this->header(); + $this->date(); + $this->metadata(); + $this->url(); + $this->visible(); + $this->modularTwig($this->slug[0] == '_'); + $this->setPublishState(); + $this->published(); + $this->urlExtension(); + + // some extension logic + if (empty($extension)) { + $this->extension('.' . $file->getExtension()); + } else { + $this->extension($extension); + } + + // extract page language from page extension + $language = trim(basename($this->extension(), 'md'), '.') ?: null; + $this->language($language); + + return $this; + } + + /** + * Return an array with the routes of other translated languages + * @return array the page translated languages + */ + public function translatedLanguages() + { + $filename = substr($this->name, 0, -(strlen($this->extension()))); + $config = self::getGrav()['config']; + $languages = $config->get('system.languages.supported', []); + $translatedLanguages = []; + + foreach ($languages as $language) { + $path = $this->path . DS . $this->folder . DS . $filename . '.' . $language . '.md'; + if (file_exists($path)) { + $aPage = new Page(); + $aPage->init(new \SplFileInfo($path), $language . '.md'); + + $route = isset($aPage->header()->routes['default']) ? $aPage->header()->routes['default'] : $aPage->rawRoute(); + if (!$route) { + $route = $aPage->slug(); + } + + $translatedLanguages[$language] = $route; + } + } + + return $translatedLanguages; + } + + /** + * Return an array listing untranslated languages available + * @return array the page untranslated languages + */ + public function untranslatedLanguages() + { + $filename = substr($this->name, 0, -(strlen($this->extension()))); + $config = self::getGrav()['config']; + $languages = $config->get('system.languages.supported', []); + $untranslatedLanguages = []; + + foreach ($languages as $language) { + $path = $this->path . DS . $this->folder . DS . $filename . '.' . $language . '.md'; + if (!file_exists($path)) { + $untranslatedLanguages[] = $language; + } + } + + return $untranslatedLanguages; + } + + /** + * Gets and Sets the raw data + * + * @param string $var Raw content string + * + * @return Object Raw content string + */ + public function raw($var = null) + { + $file = $this->file(); + + if ($var) { + // First update file object. + if ($file) { + $file->raw($var); + } + + // Reset header and content. + $this->modified = time(); + $this->id($this->modified() . md5($this->filePath())); + $this->header = null; + $this->content = null; + $this->summary = null; + } + + return $file ? $file->raw() : ''; + } + + /** + * Gets and Sets the page frontmatter + * + * @param string|null $var + * + * @return string + */ + public function frontmatter($var = null) + { + + if ($var) { + $this->frontmatter = (string)$var; + + // Update also file object. + $file = $this->file(); + if ($file) { + $file->frontmatter((string)$var); + } + + // Force content re-processing. + $this->id(time() . md5($this->filePath())); + } + if (!$this->frontmatter) { + $this->header(); + } + + return $this->frontmatter; + } + + /** + * Gets and Sets the header based on the YAML configuration at the top of the .md file + * + * @param object|array $var a YAML object representing the configuration for the file + * + * @return object the current YAML configuration + */ + public function header($var = null) + { + if ($var) { + $this->header = (object)$var; + + // Update also file object. + $file = $this->file(); + if ($file) { + $file->header((array)$var); + } + + // Force content re-processing. + $this->id(time() . md5($this->filePath())); + } + if (!$this->header) { + $file = $this->file(); + if ($file) { + // Set some options + $file->settings(['native' => true, 'compat' => true]); + try { + $this->raw_content = $file->markdown(); + $this->frontmatter = $file->frontmatter(); + $this->header = (object)$file->header(); + } catch (ParseException $e) { + $file->raw(self::getGrav()['language']->translate([ + 'FRONTMATTER_ERROR_PAGE', + $this->slug(), + $file->filename(), + $e->getMessage(), + $file->raw() + ])); + $this->raw_content = $file->markdown(); + $this->frontmatter = $file->frontmatter(); + $this->header = (object)$file->header(); + } + $var = true; + } + } + + if ($var) { + if (isset($this->header->slug)) { + $this->slug = trim($this->header->slug); + } + if (isset($this->header->routes)) { + $this->routes = (array)($this->header->routes); + } + if (isset($this->header->title)) { + $this->title = trim($this->header->title); + } + if (isset($this->header->language)) { + $this->language = trim($this->header->language); + } + if (isset($this->header->template)) { + $this->template = trim($this->header->template); + } + if (isset($this->header->menu)) { + $this->menu = trim($this->header->menu); + } + if (isset($this->header->routable)) { + $this->routable = (bool)$this->header->routable; + } + if (isset($this->header->visible)) { + $this->visible = (bool)$this->header->visible; + } + if (isset($this->header->redirect)) { + $this->redirect = trim($this->header->redirect); + } + if (isset($this->header->order_dir)) { + $this->order_dir = trim($this->header->order_dir); + } + if (isset($this->header->order_by)) { + $this->order_by = trim($this->header->order_by); + } + if (isset($this->header->order_manual)) { + $this->order_manual = (array)$this->header->order_manual; + } + if (isset($this->header->date)) { + $this->date($this->header->date); + } + if (isset($this->header->markdown_extra)) { + $this->markdown_extra = (bool)$this->header->markdown_extra; + } + if (isset($this->header->taxonomy)) { + foreach ((array)$this->header->taxonomy as $taxonomy => $taxitems) { + $this->taxonomy[$taxonomy] = (array)$taxitems; + } + } + if (isset($this->header->max_count)) { + $this->max_count = intval($this->header->max_count); + } + if (isset($this->header->process)) { + foreach ((array)$this->header->process as $process => $status) { + $this->process[$process] = (bool)$status; + } + } + if (isset($this->header->published)) { + $this->published = (bool)$this->header->published; + } + if (isset($this->header->publish_date)) { + $this->publishDate($this->header->publish_date); + } + if (isset($this->header->unpublish_date)) { + $this->unpublishDate($this->header->unpublish_date); + } + if (isset($this->header->expires)) { + $this->expires = intval($this->header->expires); + } + if (isset($this->header->etag)) { + $this->etag = (bool)$this->header->etag; + } + if (isset($this->header->last_modified)) { + $this->last_modified = (bool)$this->header->last_modified; + } + if (isset($this->header->ssl)) { + $this->ssl = (bool) $this->header->ssl; + } + } + + return $this->header; + } + + /** + * Get page language + * + * @param $var + * + * @return mixed + */ + public function language($var = null) + { + if ($var !== null) { + $this->language = $var; + } + + return $this->language; + } + + /** + * Modify a header value directly + * + * @param $key + * @param $value + */ + public function modifyHeader($key, $value) + { + $this->header->{$key} = $value; + } + + /** + * Get the summary. + * + * @param int $size Max summary size. + * + * @return string + */ + public function summary($size = null) + { + $config = (array)self::getGrav()['config']->get('site.summary'); + if (isset($this->header->summary)) { + $config = array_merge($config, $this->header->summary); + } + + // Return summary based on settings in site config file + if (!$config['enabled']) { + return $this->content(); + } + + // Set up variables to process summary from page or from custom summary + if ($this->summary === null) { + $content = $this->content(); + $summary_size = $this->summary_size; + } else { + $content = $this->summary; + $summary_size = mb_strlen($this->summary); + } + + // Return calculated summary based on summary divider's position + $format = $config['format']; + // Return entire page content on wrong/ unknown format + if (!in_array($format, ['short', 'long'])) { + return $content; + } elseif (($format === 'short') && isset($summary_size)) { + return mb_substr($content, 0, $summary_size); + } + + // Get summary size from site config's file + if (is_null($size)) { + $size = $config['size']; + } + + // If the size is zero, return the entire page content + if ($size === 0) { + return $content; + // Return calculated summary based on defaults + } elseif (!is_numeric($size) || ($size < 0)) { + $size = 300; + } + + return Utils::truncateHTML($content, $size); + } + + /** + * Sets the summary of the page + * + * @param string $summary Summary + */ + public function setSummary($summary) + { + $this->summary = $summary; + } + + /** + * Gets and Sets the content based on content portion of the .md file + * + * @param string $var Content + * + * @return string Content + */ + public function content($var = null) + { + if ($var !== null) { + $this->raw_content = $var; + + // Update file object. + $file = $this->file(); + if ($file) { + $file->markdown($var); + } + + // Force re-processing. + $this->id(time() . md5($this->filePath())); + $this->content = null; + } + // If no content, process it + if ($this->content === null) { + // Get media + $this->media(); + + /** @var Config $config */ + $config = self::getGrav()['config']; + + // Load cached content + /** @var Cache $cache */ + $cache = self::getGrav()['cache']; + $cache_id = md5('page' . $this->id()); + $this->content = $cache->fetch($cache_id); + + $process_markdown = $this->shouldProcess('markdown'); + $process_twig = $this->shouldProcess('twig'); + $cache_enable = isset($this->header->cache_enable) ? $this->header->cache_enable : $config->get('system.cache.enabled', + true); + $twig_first = isset($this->header->twig_first) ? $this->header->twig_first : $config->get('system.pages.twig_first', + true); + + + // if no cached-content run everything + if ($this->content === false || $cache_enable === false) { + $this->content = $this->raw_content; + self::getGrav()->fireEvent('onPageContentRaw', new Event(['page' => $this])); + + if ($twig_first) { + if ($process_twig) { + $this->processTwig(); + } + if ($process_markdown) { + $this->processMarkdown(); + } + + // Content Processed but not cached yet + self::getGrav()->fireEvent('onPageContentProcessed', new Event(['page' => $this])); + + } else { + if ($process_markdown) { + $this->processMarkdown(); + } + + // Content Processed but not cached yet + self::getGrav()->fireEvent('onPageContentProcessed', new Event(['page' => $this])); + + if ($process_twig) { + $this->processTwig(); + } + } + + if ($cache_enable) { + $this->cachePageContent(); + } + } + + // Handle summary divider + $delimiter = $config->get('site.summary.delimiter', '==='); + $divider_pos = mb_strpos($this->content, "

{$delimiter}

"); + if ($divider_pos !== false) { + $this->summary_size = $divider_pos; + $this->content = str_replace("

{$delimiter}

", '', $this->content); + } + + } + + return $this->content; + } + + /** + * Process the Markdown content. Uses Parsedown or Parsedown Extra depending on configuration + */ + protected function processMarkdown() + { + /** @var Config $config */ + $config = self::getGrav()['config']; + + $defaults = (array)$config->get('system.pages.markdown'); + if (isset($this->header()->markdown)) { + $defaults = array_merge($defaults, $this->header()->markdown); + } + + // pages.markdown_extra is deprecated, but still check it... + if (!isset($defaults['extra']) && (isset($this->markdown_extra) || $config->get('system.pages.markdown_extra') !== null)) { + $defaults['extra'] = $this->markdown_extra ?: $config->get('system.pages.markdown_extra'); + } + + // Initialize the preferred variant of Parsedown + if ($defaults['extra']) { + $parsedown = new ParsedownExtra($this, $defaults); + } else { + $parsedown = new Parsedown($this, $defaults); + } + + $this->content = $parsedown->text($this->content); + } + + + /** + * Process the Twig page content. + */ + private function processTwig() + { + $twig = self::getGrav()['twig']; + $this->content = $twig->processPage($this, $this->content); + } + + /** + * Fires the onPageContentProcessed event, and caches the page content using a unique ID for the page + */ + private function cachePageContent() + { + $cache = self::getGrav()['cache']; + $cache_id = md5('page' . $this->id()); + $cache->save($cache_id, $this->content); + } + + /** + * Needed by the onPageContentProcessed event to get the raw page content + * + * @return string the current page content + */ + public function getRawContent() + { + return $this->content; + } + + /** + * Needed by the onPageContentProcessed event to set the raw page content + * + * @param $content + */ + public function setRawContent($content) + { + $this->content = $content; + } + + /** + * Get value from a page variable (used mostly for creating edit forms). + * + * @param string $name Variable name. + * @param mixed $default + * + * @return mixed + */ + public function value($name, $default = null) + { + if ($name == 'content') { + return $this->raw_content; + } + if ($name == 'route') { + return $this->parent()->rawRoute(); + } + if ($name == 'order') { + $order = $this->order(); + + return $order ? (int)$this->order() : ''; + } + if ($name == 'ordering') { + return (bool)$this->order(); + } + if ($name == 'folder') { + $regex = '/^[0-9]+\./u'; + + return preg_replace($regex, '', $this->folder); + } + if ($name == 'name') { + $language = $this->language() ? '.' . $this->language() : ''; + $name_val = str_replace($language . '.md', '', $this->name()); + if ($this->modular()) { + return 'modular/' . $name_val; + } + + return $name_val; + } + if ($name == 'media') { + return $this->media()->all(); + } + if ($name == 'media.file') { + return $this->media()->files(); + } + if ($name == 'media.video') { + return $this->media()->videos(); + } + if ($name == 'media.image') { + return $this->media()->images(); + } + if ($name == 'media.audio') { + return $this->media()->audios(); + } + + $path = explode('.', $name); + $scope = array_shift($path); + + if ($name == 'frontmatter') { + return $this->frontmatter; + } + + if ($scope == 'header') { + $current = $this->header(); + foreach ($path as $field) { + if (is_object($current) && isset($current->{$field})) { + $current = $current->{$field}; + } elseif (is_array($current) && isset($current[$field])) { + $current = $current[$field]; + } else { + return $default; + } + } + + return $current; + } + + return $default; + } + + /** + * Gets and Sets the Page raw content + * + * @param null $var + * + * @return null + */ + public function rawMarkdown($var = null) + { + if ($var !== null) { + $this->raw_content = $var; + } + + return $this->raw_content; + } + + /** + * Get file object to the page. + * + * @return MarkdownFile|null + */ + public function file() + { + if ($this->name) { + return MarkdownFile::instance($this->filePath()); + } + + return null; + } + + /** + * Save page if there's a file assigned to it. + * + * @param bool $reorder Internal use. + */ + public function save($reorder = true) + { + // Perform move, copy or reordering if needed. + $this->doRelocation($reorder); + + $file = $this->file(); + if ($file) { + $file->filename($this->filePath()); + $file->header((array)$this->header()); + $file->markdown($this->raw_content); + $file->save(); + } + } + + /** + * Prepare move page to new location. Moves also everything that's under the current page. + * + * You need to call $this->save() in order to perform the move. + * + * @param Page $parent New parent page. + * + * @return $this + */ + public function move(Page $parent) + { + if (!$this->_original) { + $clone = clone $this; + $this->_original = $clone; + } + + $this->_action = 'move'; + $this->parent($parent); + $this->id(time() . md5($this->filePath())); + + if ($parent->path()) { + $this->path($parent->path() . '/' . $this->folder()); + } + + if ($parent->route()) { + $this->route($parent->route() . '/' . $this->slug()); + } else { + $this->route(self::getGrav()['pages']->root()->route() . '/' . $this->slug()); + } + + return $this; + } + + /** + * Prepare a copy from the page. Copies also everything that's under the current page. + * + * Returns a new Page object for the copy. + * You need to call $this->save() in order to perform the move. + * + * @param Page $parent New parent page. + * + * @return $this + */ + public function copy($parent) + { + $this->move($parent); + $this->_action = 'copy'; + + return $this; + } + + /** + * Get blueprints for the page. + * + * @return Blueprint + */ + public function blueprints() + { + /** @var Pages $pages */ + $pages = self::getGrav()['pages']; + + $blueprint = $pages->blueprints($this->blueprintName()); + $fields = $blueprint->fields(); + $edit_mode = isset(self::getGrav()['admin']) ? self::getGrav()['config']->get('plugins.admin.edit_mode') : null; + + // override if you only want 'normal' mode + if (empty($fields) && ($edit_mode == 'auto' || $edit_mode == 'normal')) { + $blueprint = $pages->blueprints('default'); + } + + // override if you only want 'expert' mode + if (!empty($fields) && $edit_mode == 'expert') { + $blueprint = $pages->blueprints(''); + } + + return $blueprint; + } + + /** + * Get the blueprint name for this page. Use the blueprint form field if set + * + * @return string + */ + public function blueprintName() + { + $blueprint_name = filter_input(INPUT_POST, 'blueprint', FILTER_SANITIZE_STRING) ?: $this->template(); + + return $blueprint_name; + } + + /** + * Validate page header. + * + * @throws Exception + */ + public function validate() + { + $blueprints = $this->blueprints(); + $blueprints->validate($this->toArray()); + } + + /** + * Filter page header from illegal contents. + */ + public function filter() + { + $blueprints = $this->blueprints(); + $values = $blueprints->filter($this->toArray()); + if ($values && isset($values['header'])) { + $this->header($values['header']); + } + } + + /** + * Get unknown header variables. + * + * @return array + */ + public function extra() + { + $blueprints = $this->blueprints(); + + return $blueprints->extra($this->toArray()['header'], 'header.'); + } + + /** + * Convert page to an array. + * + * @return array + */ + public function toArray() + { + return [ + 'header' => (array)$this->header(), + 'content' => (string)$this->value('content') + ]; + } + + /** + * Convert page to YAML encoded string. + * + * @return string + */ + public function toYaml() + { + return Yaml::dump($this->toArray(), 10); + } + + /** + * Convert page to JSON encoded string. + * + * @return string + */ + public function toJson() + { + return json_encode($this->toArray()); + } + + /** + * Gets and sets the associated media as found in the page folder. + * + * @param Media $var Representation of associated media. + * + * @return Media Representation of associated media. + */ + public function media($var = null) + { + /** @var Cache $cache */ + $cache = self::getGrav()['cache']; + + if ($var) { + $this->media = $var; + } + if ($this->media === null) { + // Use cached media if possible. + $media_cache_id = md5('media' . $this->id()); + if (!$media = $cache->fetch($media_cache_id)) { + $media = new Media($this->path()); + $cache->save($media_cache_id, $media); + } + $this->media = $media; + } + + return $this->media; + } + + /** + * Gets and sets the name field. If no name field is set, it will return 'default.md'. + * + * @param string $var The name of this page. + * + * @return string The name of this page. + */ + public function name($var = null) + { + if ($var !== null) { + $this->name = $var; + } + + return empty($this->name) ? 'default.md' : $this->name; + } + + /** + * Returns child page type. + * + * @return string + */ + public function childType() + { + return isset($this->header->child_type) ? (string)$this->header->child_type : 'default'; + } + + /** + * Gets and sets the template field. This is used to find the correct Twig template file to render. + * If no field is set, it will return the name without the .md extension + * + * @param string $var the template name + * + * @return string the template name + */ + public function template($var = null) + { + if ($var !== null) { + $this->template = $var; + } + if (empty($this->template)) { + $this->template = ($this->modular() ? 'modular/' : '') . str_replace($this->extension(), '', $this->name()); + } + + return $this->template; + } + + /** + * Gets and sets the extension field. + * + * @param null $var + * + * @return null|string + */ + public function extension($var = null) + { + if ($var !== null) { + $this->extension = $var; + } + if (empty($this->extension)) { + $this->extension = '.' . pathinfo($this->name(), PATHINFO_EXTENSION); + } + + return $this->extension; + } + + /** + * Returns the page extension, got from the page `url_extension` config and falls back to the + * system config `system.pages.append_url_extension`. + * + * @return string The extension of this page. For example `.html` + */ + public function urlExtension() + { + if ($this->home()) { + return ''; + } + + // if not set in the page get the value from system config + if (empty($this->url_extension)) { + $this->url_extension = trim(isset($this->header->append_url_extension) ? $this->header->append_url_extension : self::getGrav()['config']->get('system.pages.append_url_extension', false)); + } + + return $this->url_extension; + } + + /** + * Gets and sets the expires field. If not set will return the default + * + * @param int $var The new expires value. + * + * @return int The expires value + */ + public function expires($var = null) + { + if ($var !== null) { + $this->expires = $var; + } + + return empty($this->expires) ? self::getGrav()['config']->get('system.pages.expires') : $this->expires; + } + + /** + * Gets and sets the title for this Page. If no title is set, it will use the slug() to get a name + * + * @param string $var the title of the Page + * + * @return string the title of the Page + */ + public function title($var = null) + { + if ($var !== null) { + $this->title = $var; + } + if (empty($this->title)) { + $this->title = ucfirst($this->slug()); + } + + return $this->title; + } + + /** + * Gets and sets the menu name for this Page. This is the text that can be used specifically for navigation. + * If no menu field is set, it will use the title() + * + * @param string $var the menu field for the page + * + * @return string the menu field for the page + */ + public function menu($var = null) + { + if ($var !== null) { + $this->menu = $var; + } + if (empty($this->menu)) { + $this->menu = $this->title(); + } + + return $this->menu; + } + + /** + * Gets and Sets whether or not this Page is visible for navigation + * + * @param bool $var true if the page is visible + * + * @return bool true if the page is visible + */ + public function visible($var = null) + { + if ($var !== null) { + $this->visible = (bool)$var; + } + + if ($this->visible === null) { + // Set item visibility in menu if folder is different from slug + // eg folder = 01.Home and slug = Home + $regex = '/^[0-9]+\./u'; + if (preg_match($regex, $this->folder)) { + $this->visible = true; + } else { + $this->visible = false; + } + } + + return $this->visible; + } + + /** + * Gets and Sets whether or not this Page is considered published + * + * @param bool $var true if the page is published + * + * @return bool true if the page is published + */ + public function published($var = null) + { + if ($var !== null) { + $this->published = (bool)$var; + } + + // If not published, should not be visible in menus either + if ($this->published === false) { + $this->visible = false; + } + + return $this->published; + } + + /** + * Gets and Sets the Page publish date + * + * @param string $var string representation of a date + * + * @return int unix timestamp representation of the date + */ + public function publishDate($var = null) + { + if ($var !== null) { + $this->publish_date = Utils::date2timestamp($var); + } + + return $this->publish_date; + } + + /** + * Gets and Sets the Page unpublish date + * + * @param string $var string representation of a date + * + * @return int|null unix timestamp representation of the date + */ + public function unpublishDate($var = null) + { + if ($var !== null) { + $this->unpublish_date = Utils::date2timestamp($var); + } + + return $this->unpublish_date; + } + + /** + * Gets and Sets whether or not this Page is routable, ie you can reach it + * via a URL. + * The page must be *routable* and *published* + * + * @param bool $var true if the page is routable + * + * @return bool true if the page is routable + */ + public function routable($var = null) + { + if ($var !== null) { + $this->routable = (bool)$var; + } + + return $this->routable && $this->published(); + } + + public function ssl($var = null) + { + if ($var !== null) { + $this->ssl = (bool) $var; + } + + return $this->ssl; + } + + /** + * Gets and Sets the process setup for this Page. This is multi-dimensional array that consists of + * a simple array of arrays with the form array("markdown"=>true) for example + * + * @param array $var an Array of name value pairs where the name is the process and value is true or false + * + * @return array an Array of name value pairs where the name is the process and value is true or false + */ + public function process($var = null) + { + if ($var !== null) { + $this->process = (array)$var; + } + + return $this->process; + } + + /** + * Function to merge page metadata tags and build an array of Metadata objects + * that can then be rendered in the page. + * + * @param array $var an Array of metadata values to set + * + * @return array an Array of metadata values for the page + */ + public function metadata($var = null) + { + if ($var !== null) { + $this->metadata = (array)$var; + } + + // if not metadata yet, process it. + if (null === $this->metadata) { + $header_tag_http_equivs = ['content-type', 'default-style', 'refresh']; + + $this->metadata = []; + + $metadata = []; + // Set the Generator tag + $metadata['generator'] = 'GravCMS'; + + // Get initial metadata for the page + $metadata = array_merge($metadata, self::getGrav()['config']->get('site.metadata')); + + if (isset($this->header->metadata)) { + // Merge any site.metadata settings in with page metadata + $metadata = array_merge($metadata, $this->header->metadata); + } + + // Build an array of meta objects.. + foreach ((array)$metadata as $key => $value) { + // If this is a property type metadata: "og", "twitter", "facebook" etc + // Backward compatibility for nested arrays in metas + if (is_array($value)) { + foreach ($value as $property => $prop_value) { + $prop_key = $key . ":" . $property; + $this->metadata[$prop_key] = ['name' => $prop_key, 'property' => $prop_key, 'content' => htmlspecialchars($prop_value, ENT_QUOTES)]; + } + } else { + // If it this is a standard meta data type + if ($value) { + if (in_array($key, $header_tag_http_equivs)) { + $this->metadata[$key] = ['http_equiv' => $key, 'content' => htmlspecialchars($value, ENT_QUOTES)]; + } else { + // if it's a social metadata with separator, render as property + $separator = strpos($key, ':'); + $hasSeparator = $separator && $separator < strlen($key) - 1; + $entry = ['name' => $key, 'content' => htmlspecialchars($value, ENT_QUOTES)]; + + if ($hasSeparator) { + $entry['property'] = $key; + } + + $this->metadata[$key] = $entry; + } + } + } + } + } + + return $this->metadata; + } + + /** + * Gets and Sets the slug for the Page. The slug is used in the URL routing. If not set it uses + * the parent folder from the path + * + * @param string $var the slug, e.g. 'my-blog' + * + * @return string the slug + */ + public function slug($var = null) + { + if ($var !== null) { + $this->slug = $var; + } + + if (empty($this->slug)) { + $this->slug = preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->folder); + } + + return $this->slug; + } + + /** + * Get/set order number of this page. + * + * @param int $var + * + * @return int|bool + */ + public function order($var = null) + { + if ($var !== null) { + $order = !empty($var) ? sprintf('%02d.', (int)$var) : ''; + $this->folder($order . $this->slug()); + } + preg_match(PAGE_ORDER_PREFIX_REGEX, $this->folder, $order); + + return isset($order[0]) ? $order[0] : false; + } + + /** + * Gets the URL with host information, aka Permalink. + * @return string The permalink. + */ + public function permalink() + { + return $this->url(true); + } + + /** + * Gets the URL for a page - alias of url(). + * + * @param bool $include_host + * + * @return string the permalink + */ + public function link($include_host = false) + { + return $this->url($include_host); + } + + /** + * Gets the url for the Page. + * + * @param bool $include_host Defaults false, but true would include http://yourhost.com + * @param bool $canonical true to return the canonical URL + * @param bool $include_lang + * + * @return string The url. + */ + public function url($include_host = false, $canonical = false, $include_lang = true) + { + /** @var Pages $pages */ + $pages = self::getGrav()['pages']; + + /** @var Config $config */ + $config = self::getGrav()['config']; + + /** @var Language $language */ + $language = self::getGrav()['language']; + + /** @var Uri $uri */ + $uri = self::getGrav()['uri']; + + // get pre-route + if ($include_lang && $language->enabled()) { + $pre_route = $language->getLanguageURLPrefix(); + } else { + $pre_route = ''; + } + + // add full route if configured to do so + if ($config->get('system.absolute_urls', false)) { + $include_host = true; + } + + // get canonical route if requested + if ($canonical) { + $route = $pre_route . $this->routeCanonical(); + } else { + $route = $pre_route . $this->route(); + } + + $rootUrl = $uri->rootUrl($include_host) . $pages->base(); + + $url = $rootUrl . '/' . trim($route, '/') . $this->urlExtension(); + + // trim trailing / if not root + if ($url !== '/') { + $url = rtrim($url, '/'); + } + + return $url; + } + + /** + * Gets the route for the page based on the route headers if available, else from + * the parents route and the current Page's slug. + * + * @param string $var Set new default route. + * + * @return string The route for the Page. + */ + public function route($var = null) + { + if ($var !== null) { + $this->route = $var; + } + + if (empty($this->route)) { + $baseRoute = null; + + // calculate route based on parent slugs + $parent = $this->parent(); + if (isset($parent)) { + if ($this->hide_home_route && $parent->route() == $this->home_route) { + $baseRoute = ''; + } else { + $baseRoute = (string)$parent->route(); + } + } + + $this->route = isset($baseRoute) ? $baseRoute . '/' . $this->slug() : null; + + if (!empty($this->routes) && isset($this->routes['default'])) { + $this->routes['aliases'][] = $this->route; + $this->route = $this->routes['default']; + + return $this->route; + } + } + + return $this->route; + } + + /** + * Helper method to clear the route out so it regenerates next time you use it + */ + public function unsetRouteSlug() + { + unset($this->route); + unset($this->slug); + } + + /** + * Gets and Sets the page raw route + * + * @param null $var + * + * @return null|string + */ + public function rawRoute($var = null) + { + if ($var !== null) { + $this->raw_route = $var; + } + + if (empty($this->raw_route)) { + $baseRoute = $this->parent ? (string)$this->parent()->rawRoute() : null; + + $regex = '/^[0-9]+\./u'; + $slug = preg_replace($regex, '', $this->folder); + + $this->raw_route = isset($baseRoute) ? $baseRoute . '/' . $slug : null; + } + + return $this->raw_route; + } + + /** + * Gets the route aliases for the page based on page headers. + * + * @param array $var list of route aliases + * + * @return array The route aliases for the Page. + */ + public function routeAliases($var = null) + { + if ($var !== null) { + $this->routes['aliases'] = (array)$var; + } + + if (!empty($this->routes) && isset($this->routes['aliases'])) { + return $this->routes['aliases']; + } else { + return []; + } + } + + /** + * Gets the canonical route for this page if its set. If provided it will use + * that value, else if it's `true` it will use the default route. + * + * @param null $var + * + * @return bool|string + */ + public function routeCanonical($var = null) + { + if ($var !== null) { + $this->routes['canonical'] = (array)$var; + } + + if (!empty($this->routes) && isset($this->routes['canonical'])) { + return $this->routes['canonical']; + } + + return $this->route(); + } + + /** + * Gets and sets the identifier for this Page object. + * + * @param string $var the identifier + * + * @return string the identifier + */ + public function id($var = null) + { + if ($var !== null) { + $this->id = $var; + } + + return $this->id; + } + + /** + * Gets and sets the modified timestamp. + * + * @param int $var modified unix timestamp + * + * @return int modified unix timestamp + */ + public function modified($var = null) + { + if ($var !== null) { + $this->modified = $var; + } + + return $this->modified; + } + + /** + * Gets the redirect set in the header. + * + * @param string $var redirect url + * + * @return array + */ + public function redirect($var = null) + { + if ($var !== null) { + $this->redirect = $var; + } + + return $this->redirect; + } + + /** + * Gets and sets the option to show the etag header for the page. + * + * @param boolean $var show etag header + * + * @return boolean show etag header + */ + public function eTag($var = null) + { + if ($var !== null) { + $this->etag = $var; + } + if (!isset($this->etag)) { + $this->etag = (bool)self::getGrav()['config']->get('system.pages.etag'); + } + + return $this->etag; + } + + /** + * Gets and sets the option to show the last_modified header for the page. + * + * @param boolean $var show last_modified header + * + * @return boolean show last_modified header + */ + public function lastModified($var = null) + { + if ($var !== null) { + $this->last_modified = $var; + } + if (!isset($this->last_modified)) { + $this->last_modified = (bool)self::getGrav()['config']->get('system.pages.last_modified'); + } + + return $this->last_modified; + } + + /** + * Gets and sets the path to the .md file for this Page object. + * + * @param string $var the file path + * + * @return string|null the file path + */ + public function filePath($var = null) + { + if ($var !== null) { + // Filename of the page. + $this->name = basename($var); + // Folder of the page. + $this->folder = basename(dirname($var)); + // Path to the page. + $this->path = dirname(dirname($var)); + } + + return $this->path . '/' . $this->folder . '/' . ($this->name ?: ''); + } + + /** + * Gets the relative path to the .md file + * + * @return string The relative file path + */ + public function filePathClean() + { + $path = str_replace(ROOT_DIR, '', $this->filePath()); + + return $path; + } + + /** + * Returns the clean path to the page file + */ + public function relativePagePath() + { + $path = str_replace('/' . $this->name, '', $this->filePathClean()); + + return $path; + } + + /** + * Gets and sets the path to the folder where the .md for this Page object resides. + * This is equivalent to the filePath but without the filename. + * + * @param string $var the path + * + * @return string|null the path + */ + public function path($var = null) + { + if ($var !== null) { + // Folder of the page. + $this->folder = basename($var); + // Path to the page. + $this->path = dirname($var); + } + + return $this->path ? $this->path . '/' . $this->folder : null; + } + + /** + * Get/set the folder. + * + * @param string $var Optional path + * + * @return string|null + */ + public function folder($var = null) + { + if ($var !== null) { + $this->folder = $var; + } + + return $this->folder; + } + + /** + * Gets and sets the date for this Page object. This is typically passed in via the page headers + * + * @param string $var string representation of a date + * + * @return int unix timestamp representation of the date + */ + public function date($var = null) + { + if ($var !== null) { + $this->date = Utils::date2timestamp($var); + } + + if (!$this->date) { + $this->date = $this->modified; + } + + return $this->date; + } + + /** + * Gets and sets the order by which any sub-pages should be sorted. + * + * @param string $var the order, either "asc" or "desc" + * + * @return string the order, either "asc" or "desc" + */ + public function orderDir($var = null) + { + if ($var !== null) { + $this->order_dir = $var; + } + if (empty($this->order_dir)) { + $this->order_dir = 'asc'; + } + + return $this->order_dir; + } + + /** + * Gets and sets the order by which the sub-pages should be sorted. + * + * default - is the order based on the file system, ie 01.Home before 02.Advark + * title - is the order based on the title set in the pages + * date - is the order based on the date set in the pages + * folder - is the order based on the name of the folder with any numerics omitted + * + * @param string $var supported options include "default", "title", "date", and "folder" + * + * @return string supported options include "default", "title", "date", and "folder" + */ + public function orderBy($var = null) + { + if ($var !== null) { + $this->order_by = $var; + } + + return $this->order_by; + } + + /** + * Gets the manual order set in the header. + * + * @param string $var supported options include "default", "title", "date", and "folder" + * + * @return array + */ + public function orderManual($var = null) + { + if ($var !== null) { + $this->order_manual = $var; + } + + return (array)$this->order_manual; + } + + /** + * Gets and sets the maxCount field which describes how many sub-pages should be displayed if the + * sub_pages header property is set for this page object. + * + * @param int $var the maximum number of sub-pages + * + * @return int the maximum number of sub-pages + */ + public function maxCount($var = null) + { + if ($var !== null) { + $this->max_count = (int)$var; + } + if (empty($this->max_count)) { + /** @var Config $config */ + $config = self::getGrav()['config']; + $this->max_count = (int)$config->get('system.pages.list.count'); + } + + return $this->max_count; + } + + /** + * Gets and sets the taxonomy array which defines which taxonomies this page identifies itself with. + * + * @param array $var an array of taxonomies + * + * @return array an array of taxonomies + */ + public function taxonomy($var = null) + { + if ($var !== null) { + $this->taxonomy = $var; + } + + return $this->taxonomy; + } + + /** + * Gets and sets the modular var that helps identify this parent page contains modular pages. + * + * @param bool $var true if modular_twig + * + * @return bool true if modular_twig + */ + public function modular($var = null) + { + return $this->modularTwig($var); + } + + /** + * Gets and sets the modular_twig var that helps identify this page as a modular page that will need + * twig processing handled differently from a regular page. + * + * @param bool $var true if modular_twig + * + * @return bool true if modular_twig + */ + public function modularTwig($var = null) + { + if ($var !== null) { + $this->modular_twig = (bool)$var; + if ($var) { + $this->process['twig'] = true; + $this->visible(false); + // some routable logic + if (empty($this->header->routable)) { + $this->routable = false; + } + } + } + + return $this->modular_twig; + } + + /** + * Gets the configured state of the processing method. + * + * @param string $process the process, eg "twig" or "markdown" + * + * @return bool whether or not the processing method is enabled for this Page + */ + public function shouldProcess($process) + { + return isset($this->process[$process]) ? (bool)$this->process[$process] : false; + } + + /** + * Gets and Sets the parent object for this page + * + * @param Page $var the parent page object + * + * @return Page|null the parent page object if it exists. + */ + public function parent(Page $var = null) + { + if ($var) { + $this->parent = $var->path(); + + return $var; + } + + /** @var Pages $pages */ + $pages = self::getGrav()['pages']; + + return $pages->get($this->parent); + } + + /** + * Gets the top parent object for this page + * + * @return Page|null the top parent page object if it exists. + */ + public function topParent() + { + $topParent = $this->parent(); + + if (!$topParent) { + return null; + } + + while (true) { + $theParent = $topParent->parent(); + if ($theParent !== null && $theParent->parent() !== null) { + $topParent = $theParent; + } else { + break; + } + } + + return $topParent; + } + + /** + * Returns children of this page. + * + * @return \Grav\Common\Page\Collection + */ + public function children() + { + /** @var Pages $pages */ + $pages = self::getGrav()['pages']; + + return $pages->children($this->path()); + } + + + /** + * Check to see if this item is the first in an array of sub-pages. + * + * @return boolean True if item is first. + */ + public function isFirst() + { + $collection = $this->parent()->collection('content', false); + if ($collection instanceof Collection) { + return $collection->isFirst($this->path()); + } + + return true; + } + + /** + * Check to see if this item is the last in an array of sub-pages. + * + * @return boolean True if item is last + */ + public function isLast() + { + $collection = $this->parent()->collection('content', false); + if ($collection instanceof Collection) { + return $collection->isLast($this->path()); + } + + return true; + } + + /** + * Gets the previous sibling based on current position. + * + * @return Page the previous Page item + */ + public function prevSibling() + { + return $this->adjacentSibling(-1); + } + + /** + * Gets the next sibling based on current position. + * + * @return Page the next Page item + */ + public function nextSibling() + { + return $this->adjacentSibling(1); + } + + /** + * Returns the adjacent sibling based on a direction. + * + * @param integer $direction either -1 or +1 + * + * @return Page the sibling page + */ + public function adjacentSibling($direction = 1) + { + $collection = $this->parent()->collection('content', false); + if ($collection instanceof Collection) { + return $collection->adjacentSibling($this->path(), $direction); + } + + return false; + } + + /** + * Returns whether or not this page is the currently active page requested via the URL. + * + * @return bool True if it is active + */ + public function active() + { + $uri_path = rtrim(self::getGrav()['uri']->path(), '/') ?: '/'; + $routes = self::getGrav()['pages']->routes(); + + if (isset($routes[$uri_path])) { + if ($routes[$uri_path] == $this->path()) { + return true; + } + + } + + return false; + } + + /** + * Returns whether or not this URI's URL contains the URL of the active page. + * Or in other words, is this page's URL in the current URL + * + * @return bool True if active child exists + */ + public function activeChild() + { + $uri = self::getGrav()['uri']; + $pages = self::getGrav()['pages']; + $uri_path = rtrim($uri->path(), '/'); + $routes = self::getGrav()['pages']->routes(); + + if (isset($routes[$uri_path])) { + /** @var Page $child_page */ + $child_page = $pages->dispatch($uri->route())->parent(); + if ($child_page) { + while (!$child_page->root()) { + if ($this->path() == $child_page->path()) { + return true; + } + $child_page = $child_page->parent(); + } + } + } + + return false; + } + + /** + * Returns whether or not this page is the currently configured home page. + * + * @return bool True if it is the homepage + */ + public function home() + { + return $this->find('/') == $this; + } + + /** + * Returns whether or not this page is the root node of the pages tree. + * + * @return bool True if it is the root + */ + public function root() + { + if (!$this->parent && !$this->name && !$this->visible) { + return true; + } else { + return false; + } + } + + /** + * Helper method to return a 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 + */ + public function find($url, $all = false) + { + /** @var Pages $pages */ + $pages = self::getGrav()['pages']; + + return $pages->dispatch($url, $all); + } + + /** + * Get a collection of pages in the current context. + * + * @param string|array $params + * @param boolean $pagination + * + * @return Collection + * @throws \InvalidArgumentException + */ + public function collection($params = 'content', $pagination = true) + { + if (is_string($params)) { + $params = (array)$this->value('header.' . $params); + } elseif (!is_array($params)) { + throw new \InvalidArgumentException('Argument should be either header variable name or array of parameters'); + } + + if (!isset($params['items'])) { + return []; + } + + $collection = $this->evaluate($params['items']); + if (!$collection instanceof Collection) { + $collection = new Collection(); + } + $collection->setParams($params); + + /** @var Uri $uri */ + $uri = self::getGrav()['uri']; + /** @var Config $config */ + $config = self::getGrav()['config']; + + $process_taxonomy = isset($params['url_taxonomy_filters']) ? $params['url_taxonomy_filters'] : $config->get('system.pages.url_taxonomy_filters'); + + if ($process_taxonomy) { + foreach ((array)$config->get('site.taxonomies') as $taxonomy) { + if ($uri->param($taxonomy)) { + $items = explode(',', $uri->param($taxonomy)); + $collection->setParams(['taxonomies' => [$taxonomy => $items]]); + + foreach ($collection as $page) { + // Don't filter modular pages + if ($page->modular()) { + continue; + } + foreach ($items as $item) { + if (empty($page->taxonomy[$taxonomy]) || !in_array(htmlspecialchars_decode($item, + ENT_QUOTES), $page->taxonomy[$taxonomy]) + ) { + $collection->remove(); + } + } + } + } + } + } + + if (isset($params['dateRange'])) { + $start = isset($params['dateRange']['start']) ? $params['dateRange']['start'] : 0; + $end = isset($params['dateRange']['end']) ? $params['dateRange']['end'] : false; + $field = isset($params['dateRange']['field']) ? $params['dateRange']['field'] : false; + $collection->dateRange($start, $end, $field); + } + + if (isset($params['order'])) { + $by = isset($params['order']['by']) ? $params['order']['by'] : 'default'; + $dir = isset($params['order']['dir']) ? $params['order']['dir'] : 'asc'; + $custom = isset($params['order']['custom']) ? $params['order']['custom'] : null; + $collection->order($by, $dir, $custom); + } + + /** @var Grav $grav */ + $grav = self::getGrav()['grav']; + + // New Custom event to handle things like pagination. + $grav->fireEvent('onCollectionProcessed', new Event(['collection' => $collection])); + + // Slice and dice the collection if pagination is required + if ($pagination) { + $params = $collection->params(); + + $limit = isset($params['limit']) ? $params['limit'] : 0; + $start = !empty($params['pagination']) ? ($uri->currentPage() - 1) * $limit : 0; + + if ($limit && $collection->count() > $limit) { + $collection->slice($start, $limit); + } + } + + return $collection; + } + + /** + * @param string $value + * + * @return mixed + * @internal + */ + public function evaluate($value) + { + // Parse command. + if (is_string($value)) { + // Format: @command.param + $cmd = $value; + $params = []; + } elseif (is_array($value) && count($value) == 1 && !is_int(key($value))) { + // Format: @command.param: { attr1: value1, attr2: value2 } + $cmd = (string)key($value); + $params = (array)current($value); + } else { + $result = []; + foreach ($value as $key => $val) { + if (is_int($key)) { + $result = $result + $this->evaluate($val)->toArray(); + } else { + $result = $result + $this->evaluate([$key => $val])->toArray(); + } + + } + + return new Collection($result); + } + + // We only evaluate commands which start with @ + if (empty($cmd) || $cmd[0] != '@') { + return $value; + } + + /** @var Pages $pages */ + $pages = self::getGrav()['pages']; + + $parts = explode('.', $cmd); + $current = array_shift($parts); + + /** @var Collection $results */ + $results = new Collection(); + + switch ($current) { + case '@self': + if (!empty($parts)) { + switch ($parts[0]) { + case 'modular': + // @self.modular: false (alternative to @self.children) + if (!empty($params) && $params[0] === false) { + $results = $this->children()->nonModular(); + break; + } + $results = $this->children()->modular(); + break; + case 'children': + $results = $this->children()->nonModular(); + break; + case 'all': + $results = $this->children(); + break; + case 'parent': + $collection = new Collection(); + $results = $collection->addPage($this->parent()); + break; + case 'siblings': + $results = $this->parent()->children()->remove($this->path()); + break; + case 'descendants': + $results = $pages->all($this)->remove($this->path())->nonModular(); + break; + } + } + + $results = $results->published(); + break; + + case '@page': + $page = null; + + if (!empty($params)) { + $page = $this->find($params[0]); + } + + // safety check in case page is not found + if (!isset($page)) { + return $results; + } + + // Handle a @page.descendants + if (!empty($parts)) { + switch ($parts[0]) { + case 'self': + $results = new Collection(); + $results = $results->addPage($page); + break; + + case 'descendants': + $results = $pages->all($page)->remove($page->path()); + break; + + case 'children': + $results = $page->children(); + break; + } + } else { + $results = $page->children(); + } + + $results = $results->nonModular()->published(); + + break; + + case '@root': + if (!empty($parts) && $parts[0] == 'descendants') { + $results = $pages->all($pages->root())->nonModular()->published(); + } else { + $results = $pages->root()->children()->nonModular()->published(); + } + break; + + + case '@taxonomy': + // Gets a collection of pages by using one of the following formats: + // @taxonomy.category: blog + // @taxonomy.category: [ blog, featured ] + // @taxonomy: { category: [ blog, featured ], level: 1 } + + /** @var Taxonomy $taxonomy_map */ + $taxonomy_map = self::getGrav()['taxonomy']; + + if (!empty($parts)) { + $params = [implode('.', $parts) => $params]; + } + $results = $taxonomy_map->findTaxonomy($params)->published(); + break; + } + + return $results; + } + + /** + * Returns whether or not this Page object has a .md file associated with it or if its just a directory. + * + * @return bool True if its a page with a .md file associated + */ + public function isPage() + { + if ($this->name) { + return true; + } + + return false; + } + + /** + * Returns whether or not this Page object is a directory or a page. + * + * @return bool True if its a directory + */ + public function isDir() + { + return !$this->isPage(); + } + + /** + * Returns whether the page exists in the filesystem. + * + * @return bool + */ + public function exists() + { + $file = $this->file(); + + return $file && $file->exists(); + } + + /** + * Cleans the path. + * + * @param string $path the path + * + * @return string the path + */ + protected function cleanPath($path) + { + $lastchunk = strrchr($path, DS); + if (strpos($lastchunk, ':') !== false) { + $path = str_replace($lastchunk, '', $path); + } + + return $path; + } + + /** + * Moves or copies the page in filesystem. + * + * @internal + * + * @param bool $reorder + * + * @throws Exception + */ + protected function doRelocation($reorder) + { + if (!$this->_original) { + return; + } + + // Do reordering. + if ($reorder && $this->order() != $this->_original->order()) { + /** @var Pages $pages */ + $pages = self::getGrav()['pages']; + + $parent = $this->parent(); + + // Extract visible children from the parent page. + $list = []; + /** @var Page $page */ + foreach ($parent->children()->visible() as $page) { + if ($page->order()) { + $list[$page->slug] = $page->path(); + } + } + + // If page was moved, take it out of the list. + if ($this->_action == 'move') { + unset($list[$this->slug()]); + } + + $list = array_values($list); + + // Then add it back to the new location (if needed). + if ($this->order()) { + array_splice($list, min($this->order() - 1, count($list)), 0, [$this->path()]); + } + + // Reorder all moved pages. + foreach ($list as $order => $path) { + if ($path == $this->path()) { + // Handle current page; we do want to change ordering number, but nothing else. + $this->order($order + 1); + } else { + // Handle all the other pages. + $page = $pages->get($path); + + if ($page && $page->exists() && !$page->_action && $page->order() != $order + 1) { + $page = $page->move($parent); + $page->order($order + 1); + $page->save(false); + } + } + } + } + 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()) { + $path = $this->path(); + if (is_file($path . '/' . $this->_original->name())) { + rename($path . '/' . $this->_original->name(), $path . '/' . $this->name()); + } + } + + $this->_original = null; + } + + protected function setPublishState() + { + // Handle publishing dates if no explict published option set + if (self::getGrav()['config']->get('system.pages.publish_dates') && !isset($this->header->published)) { + // unpublish if required, if not clear cache right before page should be unpublished + if ($this->unpublishDate()) { + if ($this->unpublishDate() < time()) { + $this->published(false); + } else { + $this->published(); + self::getGrav()['cache']->setLifeTime($this->unpublishDate()); + } + } + // publish if required, if not clear cache right before page is published + if ($this->publishDate() && $this->publishDate() && $this->publishDate() > time()) { + $this->published(false); + self::getGrav()['cache']->setLifeTime($this->publishDate()); + } + } + } +} diff --git a/system/src/Grav/Common/Page/Pages.php b/system/src/Grav/Common/Page/Pages.php new file mode 100644 index 0000000..a5d80ea --- /dev/null +++ b/system/src/Grav/Common/Page/Pages.php @@ -0,0 +1,1059 @@ +grav = $c; + $this->base = ''; + } + + /** + * Get or set base path for the pages. + * + * @param string $path + * + * @return string + */ + public function base($path = null) + { + if ($path !== null) { + $path = trim($path, '/'); + $this->base = $path ? '/' . $path : null; + } + + return $this->base; + } + + /** + * Class initialization. Must be called before using this class. + */ + public function init() + { + $config = $this->grav['config']; + $this->ignore_files = $config->get('system.pages.ignore_files'); + $this->ignore_folders = $config->get('system.pages.ignore_folders'); + $this->ignore_hidden = $config->get('system.pages.ignore_hidden'); + + $this->instances = []; + $this->children = []; + $this->routes = []; + + $this->buildPages(); + } + + /** + * Get or set last modification time. + * + * @param int $modified + * + * @return int|null + */ + public function lastModified($modified = null) + { + if ($modified && $modified > $this->last_modified) { + $this->last_modified = $modified; + } + + return $this->last_modified; + } + + /** + * Returns a list of all pages. + * + * @return Page + */ + public function instances() + { + return $this->instances; + } + + /** + * Returns a list of all routes. + * + * @return array + */ + public function routes() + { + return $this->routes; + } + + /** + * Adds a page and assigns a route to it. + * + * @param Page $page Page to be added. + * @param string $route Optional route (uses route from the object if not set). + */ + public function addPage(Page $page, $route = null) + { + if (!isset($this->instances[$page->path()])) { + $this->instances[$page->path()] = $page; + } + $route = $page->route($route); + if ($page->parent()) { + $this->children[$page->parent()->path()][$page->path()] = ['slug' => $page->slug()]; + } + $this->routes[$route] = $page->path(); + } + + /** + * Sort sub-pages in a page. + * + * @param Page $page + * @param string $order_by + * @param string $order_dir + * + * @return array + */ + public function sort(Page $page, $order_by = null, $order_dir = null) + { + if ($order_by === null) { + $order_by = $page->orderBy(); + } + if ($order_dir === null) { + $order_dir = $page->orderDir(); + } + + $path = $page->path(); + $children = isset($this->children[$path]) ? $this->children[$path] : []; + + if (!$children) { + return $children; + } + + if (!isset($this->sort[$path][$order_by])) { + $this->buildSort($path, $children, $order_by, $page->orderManual()); + } + + $sort = $this->sort[$path][$order_by]; + + if ($order_dir != 'asc') { + $sort = array_reverse($sort); + } + + return $sort; + } + + /** + * @param Collection $collection + * @param $orderBy + * @param string $orderDir + * @param null $orderManual + * + * @return array + * @internal + */ + public function sortCollection(Collection $collection, $orderBy, $orderDir = 'asc', $orderManual = null) + { + $items = $collection->toArray(); + if (!$items) { + return []; + } + + $lookup = md5(json_encode($items) . json_encode($orderManual) . $orderBy . $orderDir); + if (!isset($this->sort[$lookup][$orderBy])) { + $this->buildSort($lookup, $items, $orderBy, $orderManual); + } + + $sort = $this->sort[$lookup][$orderBy]; + + if ($orderDir != 'asc') { + $sort = array_reverse($sort); + } + + return $sort; + + } + + /** + * Get a page instance. + * + * @param string $path The filesystem full path of the page + * + * @return Page + * @throws \Exception + */ + public function get($path) + { + if (!is_null($path) && !is_string($path)) { + throw new \Exception(); + } + + return isset($this->instances[(string)$path]) ? $this->instances[(string)$path] : null; + } + + /** + * Get children of the path. + * + * @param string $path + * + * @return Collection + */ + public function children($path) + { + $children = isset($this->children[(string)$path]) ? $this->children[(string)$path] : []; + + return new Collection($children, [], $this); + } + + /** + * Dispatch URI to a page. + * + * @param string $url The relative URL of the page + * @param bool $all + * + * @return Page|null + */ + public function dispatch($url, $all = false) + { + // Fetch page if there's a defined route to it. + $page = isset($this->routes[$url]) ? $this->get($this->routes[$url]) : null; + // Try without trailing slash + if (!$page && Utils::endsWith($url, '/')) { + $page = isset($this->routes[rtrim($url, '/')]) ? $this->get($this->routes[rtrim($url, '/')]) : null; + } + + // Are we in the admin? this is important! + $not_admin = !isset($this->grav['admin']); + + // If the page cannot be reached, look into site wide redirects, routes + wildcards + if (!$all && $not_admin && (!$page || ($page && !$page->routable()) || ($page && $page->redirect()))) { + + // If the page is a simple redirect, just do it. + if ($page && $page->redirect()) { + $this->grav->redirectLangSafe($page->redirect()); + } + + /** @var Config $config */ + $config = $this->grav['config']; + + // See if route matches one in the site configuration + $route = $config->get("site.routes.{$url}"); + if ($route) { + $page = $this->dispatch($route, $all); + } else { + // Try Regex style redirects + $site_redirects = $config->get("site.redirects"); + if (is_array($site_redirects)) { + foreach ((array)$site_redirects as $pattern => $replace) { + $pattern = '#' . $pattern . '#'; + try { + $found = preg_replace($pattern, $replace, $url); + if ($found != $url) { + $this->grav->redirectLangSafe($found); + } + } catch (ErrorException $e) { + $this->grav['log']->error('site.redirects: ' . $pattern . '-> ' . $e->getMessage()); + } + } + } + + // Try Regex style routes + $site_routes = $config->get("site.routes"); + if (is_array($site_routes)) { + foreach ((array)$site_routes as $pattern => $replace) { + $pattern = '#' . $pattern . '#'; + try { + $found = preg_replace($pattern, $replace, $url); + if ($found != $url) { + $page = $this->dispatch($found, $all); + } + } catch (ErrorException $e) { + $this->grav['log']->error('site.routes: ' . $pattern . '-> ' . $e->getMessage()); + } + } + } + } + } + + return $page; + } + + /** + * Get root page. + * + * @return Page + */ + public function root() + { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + return $this->instances[rtrim($locator->findResource('page://'), DS)]; + } + + /** + * Get a blueprint for a page type. + * + * @param string $type + * + * @return Blueprint + */ + public function blueprints($type) + { + if (!isset($this->blueprints)) { + $this->blueprints = new Blueprints(self::getTypes()); + } + + try { + $blueprint = $this->blueprints->get($type); + } catch (\RuntimeException $e) { + $blueprint = $this->blueprints->get('default'); + } + + if (!$blueprint->initialized) { + $this->grav->fireEvent('onBlueprintCreated', new Event(['blueprint' => $blueprint])); + $blueprint->initialized = true; + } + + return $blueprint; + } + + /** + * Get all pages + * + * @param \Grav\Common\Page\Page $current + * + * @return \Grav\Common\Page\Collection + */ + public function all(Page $current = null) + { + $all = new Collection(); + + /** @var Page $current */ + $current = $current ?: $this->root(); + + if (!$current->root()) { + $all[$current->path()] = ['slug' => $current->slug()]; + } + + foreach ($current->children() as $next) { + $all->append($this->all($next)); + } + + return $all; + } + + /** + * Get list of route/title of all pages. + * + * @param Page $current + * @param int $level + * @param bool $rawRoutes + * + * @return array + * + * @throws \RuntimeException + */ + public function getList(Page $current = null, $level = 0, $rawRoutes = false) + { + if (!$current) { + if ($level) { + throw new \RuntimeException('Internal error'); + } + + $current = $this->root(); + } + + $list = []; + + if (!$current->root()) { + if ($rawRoutes) { + $route = $current->rawRoute(); + } else { + $route = $current->route(); + } + $list[$route] = str_repeat('  ', ($level - 1) * 2) . $current->title(); + } + + foreach ($current->children() as $next) { + $list = array_merge($list, $this->getList($next, $level + 1, $rawRoutes)); + } + + return $list; + } + + /** + * Get available page types. + * + * @return Types + */ + public static function getTypes() + { + $locator = Grav::instance()['locator']; + if (!self::$types) { + self::$types = new Types(); + file_exists('theme://blueprints/') && self::$types->scanBlueprints($locator->findResources('theme://blueprints/')); + file_exists('theme://templates/') && self::$types->scanTemplates($locator->findResources('theme://templates/')); + + $event = new Event(); + $event->types = self::$types; + Grav::instance()->fireEvent('onGetPageTemplates', $event); + } + + return self::$types; + } + + /** + * Get available page types. + * + * @return array + */ + public static function types() + { + $types = self::getTypes(); + + return $types->pageSelect(); + } + + /** + * Get available page types. + * + * @return array + */ + public static function modularTypes() + { + $types = self::getTypes(); + + return $types->modularSelect(); + } + + /** + * Get template types based on page type (standard or modular) + * + * @return array + */ + public static function pageTypes() + { + /** @var Admin $admin */ + $admin = Grav::instance()['admin']; + + /** @var Page $page */ + $page = $admin->getPage($admin->route); + + if ($page && $page->modular()) { + return static::modularTypes(); + } + + return static::types(); + } + + /** + * Get access levels of the site pages + * + * @return array + */ + public function accessLevels() + { + $accessLevels = []; + foreach ($this->all() as $page) { + if (isset($page->header()->access)) { + if (is_array($page->header()->access)) { + foreach ($page->header()->access as $index => $accessLevel) { + if (is_array($accessLevel)) { + foreach ($accessLevel as $innerIndex => $innerAccessLevel) { + array_push($accessLevels, $innerIndex); + } + } else { + array_push($accessLevels, $index); + } + } + } else { + + array_push($accessLevels, $page->header()->access); + } + } + } + + return array_unique($accessLevels); + } + + /** + * Get available parents routes + * + * @return array + */ + public static function parents() + { + $rawRoutes = false; + + return self::getParents($rawRoutes); + } + + /** + * Get available parents raw routes. + * + * @return array + */ + public static function parentsRawRoutes() + { + $rawRoutes = true; + + return self::getParents($rawRoutes); + } + + /** + * Get available parents routes + * + * @param bool $rawRoutes get the raw route or the normal route + * + * @return array + */ + private static function getParents($rawRoutes) + { + $grav = Grav::instance(); + + /** @var Pages $pages */ + $pages = $grav['pages']; + + $parents = $pages->getList(null, 0, $rawRoutes); + + /** @var Admin $admin */ + $admin = $grav['admin']; + + // Remove current route from parents + if (isset($admin)) { + $page = $admin->getPage($admin->route); + $page_route = $page->route(); + if (isset($parents[$page_route])) { + unset($parents[$page_route]); + } + + } + + return $parents; + } + + /** + * Gets the home route + * + * @return string + */ + public static function getHomeRoute() + { + if (empty(self::$home_route)) { + $grav = Grav::instance(); + + /** @var Config $config */ + $config = $grav['config']; + + /** @var Language $language */ + $language = $grav['language']; + + $home = $config->get('system.home.alias'); + + if ($language->enabled()) { + $home_aliases = $config->get('system.home.aliases'); + if ($home_aliases) { + $active = $language->getActive(); + $default = $language->getDefault(); + + try { + if ($active) { + $home = $home_aliases[$active]; + } else { + $home = $home_aliases[$default]; + } + } catch (ErrorException $e) { + $home = $home_aliases[$default]; + } + + } + } + + self::$home_route = trim($home, '/'); + } + + return self::$home_route; + } + + /** + * Needed for testing where we change the home route via config + */ + public static function resetHomeRoute() + { + self::$home_route = null; + return self::getHomeRoute(); + } + + /** + * Builds pages. + * + * @internal + */ + protected function buildPages() + { + $this->sort = []; + + /** @var Config $config */ + $config = $this->grav['config']; + + /** @var Language $language */ + $language = $this->grav['language']; + + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + $pages_dir = $locator->findResource('page://'); + + if ($config->get('system.cache.enabled')) { + /** @var Cache $cache */ + $cache = $this->grav['cache']; + /** @var Taxonomy $taxonomy */ + $taxonomy = $this->grav['taxonomy']; + + // how should we check for last modified? Default is by file + switch (strtolower($config->get('system.cache.check.method', 'file'))) { + case 'none': + case 'off': + $last_modified = 0; + break; + case 'folder': + $last_modified = Folder::lastModifiedFolder($pages_dir); + break; + default: + $last_modified = Folder::lastModifiedFile($pages_dir); + } + + $page_cache_id = md5(USER_DIR . $last_modified . $language->getActive() . $config->checksum()); + + list($this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort) = $cache->fetch($page_cache_id); + if (!$this->instances) { + $this->grav['debugger']->addMessage('Page cache missed, rebuilding pages..'); + + // recurse pages and cache result + $this->resetPages($pages_dir, $page_cache_id); + + } else { + // If pages was found in cache, set the taxonomy + $this->grav['debugger']->addMessage('Page cache hit.'); + $taxonomy->taxonomy($taxonomy_map); + } + } else { + $this->recurse($pages_dir); + $this->buildRoutes(); + } + } + + /** + * Accessible method to manually reset the pages cache + * + * @param $pages_dir + * @param $page_cache_id + */ + public function resetPages($pages_dir, $page_cache_id) + { + $this->recurse($pages_dir); + $this->buildRoutes(); + + // cache if needed + if ($this->grav['config']->get('system.cache.enabled')) { + /** @var Cache $cache */ + $cache = $this->grav['cache']; + /** @var Taxonomy $taxonomy */ + $taxonomy = $this->grav['taxonomy']; + + // save pages, routes, taxonomy, and sort to cache + $cache->save($page_cache_id, [$this->instances, $this->routes, $this->children, $taxonomy->taxonomy(), $this->sort]); + } + } + + /** + * Recursive function to load & build page relationships. + * + * @param string $directory + * @param Page|null $parent + * + * @return Page + * @throws \RuntimeException + * @internal + */ + protected function recurse($directory, Page &$parent = null) + { + $directory = rtrim($directory, DS); + $page = new Page; + + /** @var Config $config */ + $config = $this->grav['config']; + + /** @var Language $language */ + $language = $this->grav['language']; + + // stuff to do at root page + if ($parent === null) { + + // Fire event for memory and time consuming plugins... + if ($config->get('system.pages.events.page')) { + $this->grav->fireEvent('onBuildPagesInitialized'); + } + } + + $page->path($directory); + if ($parent) { + $page->parent($parent); + } + + $page->orderDir($config->get('system.pages.order.dir')); + $page->orderBy($config->get('system.pages.order.by')); + + // Add into instances + if (!isset($this->instances[$page->path()])) { + $this->instances[$page->path()] = $page; + if ($parent && $page->path()) { + $this->children[$parent->path()][$page->path()] = ['slug' => $page->slug()]; + } + } else { + throw new \RuntimeException('Fatal error when creating page instances.'); + } + + $content_exists = false; + $pages_found = glob($directory . '/*' . CONTENT_EXT); + $page_extension = ''; + + if ($pages_found) { + $page_extensions = $language->getFallbackPageExtensions(); + foreach ($page_extensions as $extension) { + foreach ($pages_found as $found) { + if (preg_match('/^.*\/[0-9A-Za-z\-\_]+(' . $extension . ')$/', $found)) { + $page_found = $found; + $page_extension = $extension; + break 2; + } + } + } + } + + if ($parent && !empty($page_found)) { + $file = new \SplFileInfo($page_found); + $page->init($file, $page_extension); + + $content_exists = true; + + if ($config->get('system.pages.events.page')) { + $this->grav->fireEvent('onPageProcessed', new Event(['page' => $page])); + } + } + + // set current modified of page + $last_modified = $page->modified(); + + /** @var \DirectoryIterator $file */ + foreach (new \FilesystemIterator($directory) as $file) { + $name = $file->getFilename(); + + // Ignore all hidden files if set. + if ($this->ignore_hidden) { + if ($name && $name[0] == '.') { + continue; + } + } + + if ($file->isFile()) { + // Update the last modified if it's newer than already found + if (!in_array($file->getBasename(), $this->ignore_files) && ($modified = $file->getMTime()) > $last_modified) { + $last_modified = $modified; + } + } elseif ($file->isDir() && !in_array($file->getFilename(), $this->ignore_folders)) { + if (!$page->path()) { + $page->path($file->getPath()); + } + + $path = $directory . DS . $name; + $child = $this->recurse($path, $page); + + if (Utils::startsWith($name, '_')) { + $child->routable(false); + } + + $this->children[$page->path()][$child->path()] = ['slug' => $child->slug()]; + + if ($config->get('system.pages.events.page')) { + $this->grav->fireEvent('onFolderProcessed', new Event(['page' => $page])); + } + } + } + + // Set routability to false if no page found + if (!$content_exists) { + $page->routable(false); + } + + // Override the modified time if modular + if ($page->template() == 'modular') { + foreach ($page->collection() as $child) { + $modified = $child->modified(); + + if ($modified > $last_modified) { + $last_modified = $modified; + } + } + } + + // Override the modified and ID so that it takes the latest change into account + $page->modified($last_modified); + $page->id($last_modified . md5($page->filePath())); + + // Sort based on Defaults or Page Overridden sort order + $this->children[$page->path()] = $this->sort($page); + + return $page; + } + + /** + * @internal + */ + protected function buildRoutes() + { + /** @var $taxonomy Taxonomy */ + $taxonomy = $this->grav['taxonomy']; + + // Get the home route + $home = self::resetHomeRoute(); + + // Build routes and taxonomy map. + /** @var $page Page */ + foreach ($this->instances as $page) { + if (!$page->root()) { + // process taxonomy + $taxonomy->addTaxonomy($page); + + $route = $page->route(); + $raw_route = $page->rawRoute(); + $page_path = $page->path(); + + // add regular route + $this->routes[$route] = $page_path; + + // add raw route + if ($raw_route != $route) { + $this->routes[$raw_route] = $page_path; + } + + // add canonical route + $route_canonical = $page->routeCanonical(); + if ($route_canonical && ($route !== $route_canonical)) { + $this->routes[$route_canonical] = $page_path; + } + + // add aliases to routes list if they are provided + $route_aliases = $page->routeAliases(); + if ($route_aliases) { + foreach ($route_aliases as $alias) { + $this->routes[$alias] = $page_path; + } + } + } + } + + // Alias and set default route to home page. + if ($home && isset($this->routes['/' . $home])) { + $this->routes['/'] = $this->routes['/' . $home]; + $this->get($this->routes['/' . $home])->route('/'); + } + } + + /** + * @param string $path + * @param array $pages + * @param string $order_by + * @param array $manual + * + * @throws \RuntimeException + * @internal + */ + protected function buildSort($path, array $pages, $order_by = 'default', $manual = null) + { + $list = []; + $header_default = null; + $header_query = null; + + // do this header query work only once + if (strpos($order_by, 'header.') === 0) { + $header_query = explode('|', str_replace('header.', '', $order_by)); + if (isset($header_query[1])) { + $header_default = $header_query[1]; + } + } + + foreach ($pages as $key => $info) { + $child = isset($this->instances[$key]) ? $this->instances[$key] : null; + if (!$child) { + throw new \RuntimeException("Page does not exist: {$key}"); + } + + switch ($order_by) { + case 'title': + $list[$key] = $child->title(); + break; + case 'date': + $list[$key] = $child->date(); + break; + case 'modified': + $list[$key] = $child->modified(); + break; + case 'slug': + $list[$key] = $child->slug(); + break; + case 'basename': + $list[$key] = basename($key); + break; + case (is_string($header_query[0])): + $child_header = new Header((array)$child->header()); + $header_value = $child_header->get($header_query[0]); + if ($header_value) { + $list[$key] = $header_value; + } else { + $list[$key] = $header_default ?: $key; + } + break; + case 'manual': + case 'default': + default: + $list[$key] = $key; + } + } + + // handle special case when order_by is random + if ($order_by == 'random') { + $list = $this->arrayShuffle($list); + } else { + // else just sort the list according to specified key + asort($list); + } + + + // Move manually ordered items into the beginning of the list. Order of the unlisted items does not change. + if (is_array($manual) && !empty($manual)) { + $new_list = []; + $i = count($manual); + + foreach ($list as $key => $dummy) { + $info = $pages[$key]; + $order = array_search($info['slug'], $manual); + if ($order === false) { + $order = $i++; + } + $new_list[$key] = (int)$order; + } + + $list = $new_list; + + // Apply manual ordering to the list. + asort($list); + } + + foreach ($list as $key => $sort) { + $info = $pages[$key]; + $this->sort[$path][$order_by][$key] = $info; + } + } + + /** + * Shuffles an associative array + * + * @param array $list + * + * @return array + */ + protected function arrayShuffle($list) + { + $keys = array_keys($list); + shuffle($keys); + + $new = []; + foreach ($keys as $key) { + $new[$key] = $list[$key]; + } + + return $new; + } +} diff --git a/system/src/Grav/Common/Page/Types.php b/system/src/Grav/Common/Page/Types.php new file mode 100644 index 0000000..5fbeaea --- /dev/null +++ b/system/src/Grav/Common/Page/Types.php @@ -0,0 +1,113 @@ +systemBlueprints && isset($this->systemBlueprints[$type])) { + $useBlueprint = $this->systemBlueprints[$type]; + } else { + $useBlueprint = $blueprint; + } + + if ($blueprint || empty($this->items[$type])) { + $this->items[$type] = $useBlueprint; + } + } + + public function scanBlueprints($paths) + { + $this->items = $this->findBlueprints($paths) + $this->items; + } + + public function scanTemplates($paths) + { + $options = [ + 'compare' => 'Filename', + 'pattern' => '|\.html\.twig$|', + 'filters' => [ + 'value' => '|\.html\.twig$|' + ], + 'value' => 'Filename', + 'recursive' => false + ]; + + if (!$this->systemBlueprints) { + $this->systemBlueprints = $this->findBlueprints('blueprints://pages'); + } + + // register default by default + $this->register('default'); + + foreach ((array) $paths as $path) { + foreach (Folder::all($path, $options) as $type) { + $this->register($type); + } + $modular_path = rtrim($path, '/') . '/modular'; + if (file_exists($modular_path)) { + foreach (Folder::all($modular_path, $options) as $type) { + $this->register('modular/' . $type); + } + } + } + } + + public function pageSelect() + { + $list = []; + foreach ($this->items as $name => $file) { + if (strpos($name, '/')) { + continue; + } + $list[$name] = ucfirst(strtr($name, '_', ' ')); + } + ksort($list); + return $list; + } + + public function modularSelect() + { + $list = []; + foreach ($this->items as $name => $file) { + if (strpos($name, 'modular/') !== 0) { + continue; + } + $list[$name] = trim(ucfirst(strtr(basename($name), '_', ' '))); + } + ksort($list); + return $list; + } + + private function findBlueprints($paths) + { + $options = [ + 'compare' => 'Filename', + 'pattern' => '|\.yaml$|', + 'filters' => [ + 'key' => '|\.yaml$|' + ], + 'key' => 'SubPathName', + 'value' => 'PathName', + ]; + + $list = []; + foreach ((array) $paths as $path) { + $list += Folder::all($path, $options); + } + + return $list; + } +} diff --git a/system/src/Grav/Common/Plugin.php b/system/src/Grav/Common/Plugin.php new file mode 100644 index 0000000..e6fd416 --- /dev/null +++ b/system/src/Grav/Common/Plugin.php @@ -0,0 +1,212 @@ +grav = $grav; + $this->config = $config; + $this->name = $name; + } + + public function isAdmin() + { + if (isset($this->grav['admin'])) { + return true; + } + + return false; + } + + /** + * @param array $events + */ + protected function enable(array $events) + { + /** @var EventDispatcher $dispatcher */ + $dispatcher = $this->grav['events']; + + foreach ($events as $eventName => $params) { + if (is_string($params)) { + $dispatcher->addListener($eventName, [$this, $params]); + } elseif (is_string($params[0])) { + $dispatcher->addListener($eventName, [$this, $params[0]], isset($params[1]) ? $params[1] : 0); + } else { + foreach ($params as $listener) { + $dispatcher->addListener($eventName, [$this, $listener[0]], isset($listener[1]) ? $listener[1] : 0); + } + } + } + } + + /** + * @param array $events + */ + protected function disable(array $events) + { + /** @var EventDispatcher $dispatcher */ + $dispatcher = $this->grav['events']; + + foreach ($events as $eventName => $params) { + if (is_string($params)) { + $dispatcher->removeListener($eventName, [$this, $params]); + } elseif (is_string($params[0])) { + $dispatcher->removeListener($eventName, [$this, $params[0]]); + } else { + foreach ($params as $listener) { + $dispatcher->removeListener($eventName, [$this, $listener[0]]); + } + } + } + } + + /** + * This function will search a string for markdown links in a specific format. The link value can be + * optionally compared against via the $internal_regex and operated on by the callback $function + * provided. + * + * format: [plugin:myplugin_name](function_data) + * + * @param string $content The string to perform operations upon + * @param callable $function The anonymous callback function + * @param string $internal_regex Optional internal regex to extra data from + * + * @return string + */ + protected function parseLinks($content, $function, $internal_regex = '(.*)') + { + $regex = '/\[plugin:(?:' . $this->name . ')\]\(' . $internal_regex . '\)/i'; + + return preg_replace_callback($regex, $function, $content); + } + + /** + * Merge global and page configurations. + * + * @param Page $page The page to merge the configurations with the + * plugin settings. + * @param bool $deep Should you use deep or shallow merging + * @param array $params Array of additional configuration options to + * merge with the plugin settings. + * + * @return \Grav\Common\Data\Data + */ + protected function mergeConfig(Page $page, $deep = false, $params = []) + { + $class_name = $this->name; + $class_name_merged = $class_name . '.merged'; + $defaults = $this->config->get('plugins.' . $class_name, []); + $page_header = $page->header(); + $header = []; + if (!isset($page_header->$class_name_merged) && isset($page_header->$class_name)) { + // Get default plugin configurations and retrieve page header configuration + $config = $page_header->$class_name; + if (is_bool($config)) { + // Overwrite enabled option with boolean value in page header + $config = ['enabled' => $config]; + } + // Merge page header settings using deep or shallow merging technique + if ($deep) { + $header = array_replace_recursive($defaults, $config); + } else { + $header = array_merge($defaults, $config); + } + // Create new config object and set it on the page object so it's cached for next time + $page->modifyHeader($class_name_merged, new Data($header)); + } else if (isset($page_header->$class_name_merged)) { + $merged = $page_header->$class_name_merged; + $header = $merged->toArray(); + } + if (empty($header)) { + $header = $defaults; + } + // Merge additional parameter with configuration options + if ($deep) { + $header = array_replace_recursive($header, $params); + } else { + $header = array_merge($header, $params); + } + + // Return configurations as a new data config class + return new Data($header); + } + + /** + * Persists to disk the plugin parameters currently stored in the Grav Config object + * + * @param string $plugin_name The name of the plugin whose config it should store. + * + * @return true + */ + public static function saveConfig($plugin_name) + { + if (!$plugin_name) { + return false; + } + + $locator = Grav::instance()['locator']; + $filename = 'config://plugins/' . $plugin_name . '.yaml'; + $file = YamlFile::instance($locator->findResource($filename, true, true)); + $content = Grav::instance()['config']->get('plugins.' . $plugin_name); + $file->save($content); + $file->free(); + + return true; + } +} diff --git a/system/src/Grav/Common/Plugins.php b/system/src/Grav/Common/Plugins.php new file mode 100644 index 0000000..7088e2a --- /dev/null +++ b/system/src/Grav/Common/Plugins.php @@ -0,0 +1,159 @@ +get('plugins'); + + $inflector = self::getGrav()['inflector']; + + /** @var EventDispatcher $events */ + $events = self::getGrav()['events']; + + foreach ($plugins as $plugin => $data) { + if (empty($data['enabled'])) { + // Only load enabled plugins. + continue; + } + + $locator = self::getGrav()['locator']; + $filePath = $locator->findResource('plugins://' . $plugin . DS . $plugin . PLUGIN_EXT); + if (!is_file($filePath)) { + self::getGrav()['log']->addWarning(sprintf("Plugin '%s' enabled but not found! Try clearing cache with `bin/grav clear-cache`", $plugin)); + continue; + } + + require_once $filePath; + + $pluginClassFormat = [ + 'Grav\\Plugin\\' . ucfirst($plugin) . 'Plugin', + 'Grav\\Plugin\\' . $inflector->camelize($plugin) . 'Plugin' + ]; + $pluginClassName = false; + + foreach ($pluginClassFormat as $pluginClass) { + if (class_exists($pluginClass)) { + $pluginClassName = $pluginClass; + break; + } + } + + if (false === $pluginClassName) { + throw new \RuntimeException(sprintf("Plugin '%s' class not found! Try reinstalling this plugin.", + $plugin)); + } + + $instance = new $pluginClassName($plugin, self::getGrav(), $config); + if ($instance instanceof EventSubscriberInterface) { + $events->addSubscriber($instance); + } + } + + return $this->items; + } + + /** + * Add a plugin + * + * @param $plugin + */ + public function add($plugin) + { + if (is_object($plugin)) { + $this->items[get_class($plugin)] = $plugin; + } + } + + /** + * Return list of all plugin data with their blueprints. + * + * @return array + */ + public static function all() + { + $list = []; + $locator = Grav::instance()['locator']; + + $plugins = (array)$locator->findResources('plugins://', false); + foreach ($plugins as $path) { + $iterator = new \DirectoryIterator($path); + + /** @var \DirectoryIterator $directory */ + foreach ($iterator as $directory) { + if (!$directory->isDir() || $directory->isDot()) { + continue; + } + + $plugin = $directory->getBasename(); + $result = self::get($plugin); + + if ($result) { + $list[$plugin] = $result; + } + } + } + ksort($list); + + return $list; + } + + /** + * Get a plugin by name + * + * @param string $name + * + * @return Data|null + */ + public static function get($name) + { + $blueprints = new Blueprints('plugins://'); + $blueprint = $blueprints->get("{$name}/blueprints"); + $blueprint->name = $name; + + // Load default configuration. + $file = CompiledYamlFile::instance("plugins://{$name}/{$name}" . YAML_EXT); + + // ensure this is a valid plugin + if (!$file->exists()) { + return null; + } + + $obj = new Data($file->content(), $blueprint); + + // Override with user configuration. + $obj->merge(self::getGrav()['config']->get('plugins.' . $name) ?: []); + + // Save configuration always to user/config. + $file = CompiledYamlFile::instance("config://plugins/{$name}.yaml"); + $obj->file($file); + + return $obj; + } + +} diff --git a/system/src/Grav/Common/Service/ConfigServiceProvider.php b/system/src/Grav/Common/Service/ConfigServiceProvider.php new file mode 100644 index 0000000..79c99ed --- /dev/null +++ b/system/src/Grav/Common/Service/ConfigServiceProvider.php @@ -0,0 +1,152 @@ +findResource('cache://compiled/blueprints', true, true); + + $files = []; + $paths = $locator->findResources('blueprints://config'); + $files += (new ConfigFileFinder)->locateFiles($paths); + $paths = $locator->findResources('plugins://'); + $files += (new ConfigFileFinder)->setBase('plugins')->locateInFolders($paths, 'blueprints'); + + $blueprints = new CompiledBlueprints($cache, $files, GRAV_ROOT); + + return $blueprints->name("master-{$setup->environment}")->load(); + } + + public static function load(Container $container) + { + /** Setup $setup */ + $setup = $container['setup']; + + /** @var UniformResourceLocator $locator */ + $locator = $container['locator']; + + $cache = $locator->findResource('cache://compiled/config', true, true); + + $files = []; + $paths = $locator->findResources('config://'); + $files += (new ConfigFileFinder)->locateFiles($paths); + $paths = $locator->findResources('plugins://'); + $files += (new ConfigFileFinder)->setBase('plugins')->locateInFolders($paths); + + $config = new CompiledConfig($cache, $files, GRAV_ROOT); + $config->setBlueprints(function() use ($container) { + return $container['blueprints']; + }); + + return $config->name("master-{$setup->environment}")->load(); + } + + public static function languages(Container $container) + { + /** Setup $setup */ + $setup = $container['setup']; + + /** @var Config $config */ + $config = $container['config']; + + /** @var UniformResourceLocator $locator */ + $locator = $container['locator']; + + $cache = $locator->findResource('cache://compiled/languages', true, true); + $files = []; + + // Process languages only if enabled in configuration. + if ($config->get('system.languages.translations', true)) { + $paths = $locator->findResources('languages://'); + $files += (new ConfigFileFinder)->locateFiles($paths); + $paths = $locator->findResources('plugins://'); + $files += (new ConfigFileFinder)->setBase('plugins')->locateInFolders($paths, 'languages'); + $paths = static::pluginFolderPaths($paths, 'languages'); + $files += (new ConfigFileFinder)->locateFiles($paths); + } + + $languages = new CompiledLanguages($cache, $files, GRAV_ROOT); + + return $languages->name("master-{$setup->environment}")->load(); + } + + /** + * Find specific paths in plugins + * + * @param $plugins + * @param $folder_path + * @return array + */ + private static function pluginFolderPaths($plugins, $folder_path) + { + $paths = []; + + foreach ($plugins as $path) { + $iterator = new \DirectoryIterator($path); + + /** @var \DirectoryIterator $directory */ + foreach ($iterator as $directory) { + if (!$directory->isDir() || $directory->isDot()) { + continue; + } + + // Path to the languages folder + $lang_path = $directory->getPathName() . '/' . $folder_path; + + // If this folder exists, add it to the list of paths + if (file_exists($lang_path)) { + $paths []= $lang_path; + } + } + } + return $paths; + } + +} diff --git a/system/src/Grav/Common/Service/ErrorServiceProvider.php b/system/src/Grav/Common/Service/ErrorServiceProvider.php new file mode 100644 index 0000000..3417354 --- /dev/null +++ b/system/src/Grav/Common/Service/ErrorServiceProvider.php @@ -0,0 +1,18 @@ +pushHandler(new StreamHandler($log_file, Logger::DEBUG)); + + $container['log'] = $log; + } +} diff --git a/system/src/Grav/Common/Service/StreamsServiceProvider.php b/system/src/Grav/Common/Service/StreamsServiceProvider.php new file mode 100644 index 0000000..ec7f9cd --- /dev/null +++ b/system/src/Grav/Common/Service/StreamsServiceProvider.php @@ -0,0 +1,40 @@ +initializeLocator($locator); + + return $locator; + }; + + $container['streams'] = function($c) { + /** @var Setup $setup */ + $setup = $c['setup']; + + /** @var UniformResourceLocator $locator */ + $locator = $c['locator']; + + // Set locator to both streams. + Stream::setLocator($locator); + ReadOnlyStream::setLocator($locator); + + return new StreamBuilder($setup->getStreams($c)); + }; + } +} diff --git a/system/src/Grav/Common/Session.php b/system/src/Grav/Common/Session.php new file mode 100644 index 0000000..c73ddcf --- /dev/null +++ b/system/src/Grav/Common/Session.php @@ -0,0 +1,63 @@ +grav = $grav; + } + + /** + * Session init + */ + public function init() + { + /** @var Uri $uri */ + $uri = $this->grav['uri']; + $config = $this->grav['config']; + + $is_admin = false; + + $session_timeout = $config->get('system.session.timeout', 1800); + $session_path = $config->get('system.session.path', '/' . ltrim($uri->rootUrl(false), '/')); + + // Activate admin if we're inside the admin path. + if ($config->get('plugins.admin.enabled')) { + $route = $config->get('plugins.admin.route'); + $base = '/' . trim($route, '/'); + if (substr($uri->route(), 0, strlen($base)) == $base) { + $session_timeout = $config->get('plugins.admin.session.timeout', 1800); + $is_admin = true; + } + } + + if ($config->get('system.session.enabled') || $is_admin) { + // Define session service. + parent::__construct($session_timeout, $session_path); + + $domain = $uri->host(); + if ($domain == 'localhost') { + $domain = ''; + } + $secure = $config->get('system.session.secure', false); + $httponly = $config->get('system.session.httponly', true); + + $unique_identifier = GRAV_ROOT; + $this->setName($config->get('system.session.name', 'grav_site') . '-' . substr(md5($unique_identifier), 0, 7) . ($is_admin ? '-admin' : '')); + $this->start(); + setcookie(session_name(), session_id(), time() + $session_timeout, $session_path, $domain, $secure, $httponly); + } + } +} diff --git a/system/src/Grav/Common/Taxonomy.php b/system/src/Grav/Common/Taxonomy.php new file mode 100644 index 0000000..ea01c99 --- /dev/null +++ b/system/src/Grav/Common/Taxonomy.php @@ -0,0 +1,125 @@ +taxonomy_map = []; + $this->grav = $grav; + } + + /** + * Takes an individual page and processes the taxonomies configured in its header. It + * then adds those taxonomies to the map + * + * @param Page $page the page to process + * @param array $page_taxonomy + */ + public function addTaxonomy(Page $page, $page_taxonomy = null) + { + if (!$page_taxonomy) { + $page_taxonomy = $page->taxonomy(); + } + + if (!$page->published() || empty($page_taxonomy)) { + return; + } + + /** @var Config $config */ + $config = $this->grav['config']; + if ($config->get('site.taxonomies')) { + foreach ((array)$config->get('site.taxonomies') as $taxonomy) { + if (isset($page_taxonomy[$taxonomy])) { + foreach ((array)$page_taxonomy[$taxonomy] as $item) { + $this->taxonomy_map[$taxonomy][(string)$item][$page->path()] = ['slug' => $page->slug()]; + } + } + } + } + } + + /** + * Returns a new Page object with the sub-pages containing all the values set for a + * particular taxonomy. + * + * @param array $taxonomies taxonomies to search, eg ['tag'=>['animal','cat']] + * @param string $operator can be 'or' or 'and' (defaults to 'or') + * + * @return Collection Collection object set to contain matches found in the taxonomy map + */ + public function findTaxonomy($taxonomies, $operator = 'and') + { + $matches = []; + $results = []; + + foreach ((array)$taxonomies as $taxonomy => $items) { + foreach ((array)$items as $item) { + if (isset($this->taxonomy_map[$taxonomy][$item])) { + $matches[] = $this->taxonomy_map[$taxonomy][$item]; + } + } + } + + if (strtolower($operator) == 'or') { + foreach ($matches as $match) { + $results = array_merge($results, $match); + } + } else { + $results = $matches ? array_pop($matches) : []; + foreach ($matches as $match) { + $results = array_intersect_key($results, $match); + } + } + + return new Collection($results, ['taxonomies' => $taxonomies]); + } + + /** + * Gets and Sets the taxonomy map + * + * @param array $var the taxonomy map + * + * @return array the taxonomy map + */ + public function taxonomy($var = null) + { + if ($var) { + $this->taxonomy_map = $var; + } + + return $this->taxonomy_map; + } +} diff --git a/system/src/Grav/Common/Theme.php b/system/src/Grav/Common/Theme.php new file mode 100644 index 0000000..f76e5f0 --- /dev/null +++ b/system/src/Grav/Common/Theme.php @@ -0,0 +1,51 @@ +name = $name; + + parent::__construct($name, $grav, $config); + } + + /** + * Persists to disk the theme parameters currently stored in the Grav Config object + * + * @param string $theme_name The name of the theme whose config it should store. + * + * @return true + */ + public static function saveConfig($theme_name) + { + if (!$theme_name) { + return false; + } + + $locator = Grav::instance()['locator']; + $filename = 'config://themes/' . $theme_name . '.yaml'; + $file = YamlFile::instance($locator->findResource($filename, true, true)); + $content = Grav::instance()['config']->get('themes.' . $theme_name); + $file->save($content); + $file->free(); + + return true; + } +} diff --git a/system/src/Grav/Common/Themes.php b/system/src/Grav/Common/Themes.php new file mode 100644 index 0000000..9f360cc --- /dev/null +++ b/system/src/Grav/Common/Themes.php @@ -0,0 +1,327 @@ +grav = $grav; + $this->config = $grav['config']; + + // Register instance as autoloader for theme inheritance + spl_autoload_register([$this, 'autoloadTheme']); + } + + public function init() + { + /** @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) { + throw new \RuntimeException($this->current() . ' theme could not be found'); + } + + if ($instance instanceof EventSubscriberInterface) { + /** @var EventDispatcher $events */ + $events = $this->grav['events']; + + $events->addSubscriber($instance); + } + + $this->grav['theme'] = $instance; + + $this->grav->fireEvent('onThemeInitialized'); + } + + /** + * Return list of all theme data with their blueprints. + * + * @return array + */ + public function all() + { + $list = []; + $locator = Grav::instance()['locator']; + + $themes = (array)$locator->findResources('themes://', false); + foreach ($themes as $path) { + $iterator = new \DirectoryIterator($path); + + /** @var \DirectoryIterator $directory */ + foreach ($iterator as $directory) { + if (!$directory->isDir() || $directory->isDot()) { + continue; + } + + $theme = $directory->getBasename(); + $result = self::get($theme); + + if ($result) { + $list[$theme] = $result; + } + } + } + ksort($list); + + return $list; + } + + /** + * Get theme configuration or throw exception if it cannot be found. + * + * @param string $name + * + * @return Data + * @throws \RuntimeException + */ + public function get($name) + { + if (!$name) { + throw new \RuntimeException('Theme name not provided.'); + } + + $blueprints = new Blueprints('themes://'); + $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"; + $path = $this->grav['locator']->findResource($thumb, false); + + if ($path) { + $blueprint->set('thumbnail', $this->grav['base_url'] . '/' . $path); + } + + $obj = new Data($file->content(), $blueprint); + + // Override with user configuration. + $obj->merge($this->grav['config']->get('themes.' . $name) ?: []); + + // Save configuration always to user/config. + $file = CompiledYamlFile::instance("config://themes/{$name}" . YAML_EXT); + $obj->file($file); + + return $obj; + } + + /** + * Return name of the current theme. + * + * @return string + */ + public function current() + { + return (string)$this->config->get('system.pages.theme'); + } + + /** + * Load current theme. + * + * @return Theme|object + */ + public function load() + { + // NOTE: ALL THE LOCAL VARIABLES ARE USED INSIDE INCLUDED FILE, DO NOT REMOVE THEM! + $grav = $this->grav; + $config = $this->config; + $name = $this->current(); + + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + $file = $locator('theme://theme.php') ?: $locator("theme://{$name}.php"); + + $inflector = $grav['inflector']; + + if ($file) { + // Local variables available in the file: $grav, $config, $name, $file + $class = include $file; + + if (!is_object($class)) { + $themeClassFormat = [ + 'Grav\\Theme\\' . ucfirst($name), + 'Grav\\Theme\\' . $inflector->camelize($name) + ]; + + foreach ($themeClassFormat as $themeClass) { + if (class_exists($themeClass)) { + $themeClassName = $themeClass; + $class = new $themeClassName($grav, $config, $name); + break; + } + } + } + } elseif (!$locator('theme://') && !defined('GRAV_CLI')) { + exit("Theme '$name' does not exist, unable to display page."); + } + + $this->config->set('theme', $config->get('themes.' . $name)); + + if (empty($class)) { + $class = new Theme($grav, $config, $name); + } + + return $class; + } + + /** + * Configure and prepare streams for current template. + * + * @throws \InvalidArgumentException + */ + public function configure() + { + $name = $this->current(); + $config = $this->config; + + $this->loadConfiguration($name, $config); + + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + + $registered = stream_get_wrappers(); + $schemes = $config->get( + "themes.{$name}.streams.schemes", + ['theme' => ['paths' => $locator->findResources("themes://{$name}", false)]] + ); + + foreach ($schemes as $scheme => $config) { + if (isset($config['paths'])) { + $locator->addPath($scheme, '', $config['paths']); + } + if (isset($config['prefixes'])) { + foreach ($config['prefixes'] as $prefix => $paths) { + $locator->addPath($scheme, $prefix, $paths); + } + } + + if (in_array($scheme, $registered)) { + stream_wrapper_unregister($scheme); + } + $type = !empty($config['type']) ? $config['type'] : 'ReadOnlyStream'; + if ($type[0] != '\\') { + $type = '\\RocketTheme\\Toolbox\\StreamWrapper\\' . $type; + } + + if (!stream_wrapper_register($scheme, $type)) { + throw new \InvalidArgumentException("Stream '{$type}' could not be initialized."); + } + } + + // Load languages after streams has been properly initialized + $this->loadLanguages($this->config); + } + + /** + * Load theme configuration. + * + * @param string $name Theme name + * @param Config $config Configuration class + */ + protected function loadConfiguration($name, Config $config) + { + $themeConfig = CompiledYamlFile::instance("themes://{$name}/{$name}" . YAML_EXT)->content(); + $config->joinDefaults("themes.{$name}", $themeConfig); + } + + /** + * Load theme languages. + * + * @param Config $config Configuration class + */ + protected function loadLanguages(Config $config) + { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + + if ($config->get('system.languages.translations', true)) { + $languageFiles = array_reverse($locator->findResources("theme://languages" . YAML_EXT)); + + $languages = []; + foreach ($languageFiles as $language) { + $languages[] = CompiledYamlFile::instance($language)->content(); + } + + if ($languages) { + $languages = call_user_func_array('array_replace_recursive', $languages); + $this->grav['languages']->mergeRecursive($languages); + } + } + } + + /** + * Autoload theme classes for inheritance + * + * @param string $class Class name + * + * @return mixed false FALSE if unable to load $class; Class name if + * $class is successfully loaded + */ + protected function autoloadTheme($class) + { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + + $prefix = "Grav\\Theme"; + if (false !== strpos($class, $prefix)) { + // Remove prefix from class + $class = substr($class, strlen($prefix)); + + // Replace namespace tokens to directory separators + $path = strtolower(ltrim(preg_replace('#\\\|_(?!.+\\\)#', '/', $class), '/')); + $file = $locator->findResource("themes://{$path}/{$path}.php"); + + // Load class + if (file_exists($file)) { + return include_once($file); + } + } + + return false; + } +} diff --git a/system/src/Grav/Common/Twig/Twig.php b/system/src/Grav/Common/Twig/Twig.php new file mode 100644 index 0000000..16acf18 --- /dev/null +++ b/system/src/Grav/Common/Twig/Twig.php @@ -0,0 +1,368 @@ +grav = $grav; + $this->twig_paths = []; + } + + /** + * Twig initialization that sets the twig loader chain, then the environment, then extensions + * and also the base set of twig vars + */ + public function init() + { + if (!isset($this->twig)) { + /** @var Config $config */ + $config = $this->grav['config']; + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + + /** @var Language $language */ + $language = $this->grav['language']; + + $active_language = $language->getActive(); + + $language_append = ''; + if ($language->getDefault() != $active_language || $config->get('system.languages.include_default_lang') === true) { + $language_append = $active_language ? '/' . $active_language : ''; + } + + // handle language templates if available + if ($language->enabled()) { + $lang_templates = $locator->findResource('theme://templates/' . ($active_language ? $active_language : $language->getDefault())); + if ($lang_templates) { + $this->twig_paths[] = $lang_templates; + } + } + + $this->twig_paths = array_merge($this->twig_paths, $locator->findResources('theme://templates')); + + $this->grav->fireEvent('onTwigTemplatePaths'); + + $this->loader = new \Twig_Loader_Filesystem($this->twig_paths); + $this->loaderArray = new \Twig_Loader_Array([]); + $loader_chain = new \Twig_Loader_Chain([$this->loaderArray, $this->loader]); + + $params = $config->get('system.twig'); + if (!empty($params['cache'])) { + $params['cache'] = $locator->findResource('cache://twig', true, true); + } + + $this->twig = new TwigEnvironment($loader_chain, $params); + + if ($config->get('system.twig.undefined_functions')) { + $this->twig->registerUndefinedFunctionCallback(function ($name) { + if (function_exists($name)) { + return new \Twig_Function_Function($name); + } + + return new \Twig_Function_Function(function () { + }); + }); + } + + if ($config->get('system.twig.undefined_filters')) { + $this->twig->registerUndefinedFilterCallback(function ($name) { + if (function_exists($name)) { + return new \Twig_Filter_Function($name); + } + + return new \Twig_Filter_Function(function () { + }); + }); + } + + $this->grav->fireEvent('onTwigInitialized'); + + // set default date format if set in config + if ($config->get('system.pages.dateformat.long')) { + $this->twig->getExtension('core')->setDateFormat($config->get('system.pages.dateformat.long')); + } + // enable the debug extension if required + if ($config->get('system.twig.debug')) { + $this->twig->addExtension(new \Twig_Extension_Debug()); + } + $this->twig->addExtension(new TwigExtension()); + + $this->grav->fireEvent('onTwigExtensions'); + + // Set some standard variables for twig + $this->twig_vars = $this->twig_vars + [ + 'config' => $config, + 'uri' => $this->grav['uri'], + 'base_dir' => rtrim(ROOT_DIR, '/'), + 'base_url' => $this->grav['base_url'] . $language_append, + 'base_url_simple' => $this->grav['base_url'], + 'base_url_absolute' => $this->grav['base_url_absolute'] . $language_append, + 'base_url_relative' => $this->grav['base_url_relative'] . $language_append, + 'theme_dir' => $locator->findResource('theme://'), + 'theme_url' => $this->grav['base_url'] . '/' . $locator->findResource('theme://', false), + 'site' => $config->get('site'), + 'assets' => $this->grav['assets'], + 'taxonomy' => $this->grav['taxonomy'], + 'browser' => $this->grav['browser'], + ]; + } + } + + /** + * @return \Twig_Environment + */ + public function twig() + { + return $this->twig; + } + + /** + * @return \Twig_Loader_Filesystem + */ + public function loader() + { + return $this->loader; + } + + /** + * Adds or overrides a template. + * + * @param string $name The template name + * @param string $template The template source + */ + public function setTemplate($name, $template) + { + $this->loaderArray->setTemplate($name, $template); + } + + /** + * Twig process that renders a page item. It supports two variations: + * 1) Handles modular pages by rendering a specific page based on its modular twig template + * 2) Renders individual page items for twig processing before the site rendering + * + * @param Page $item The page item to render + * @param string $content Optional content override + * + * @return string The rendered output + * @throws \Twig_Error_Loader + */ + public function processPage(Page $item, $content = null) + { + $content = $content !== null ? $content : $item->content(); + + // override the twig header vars for local resolution + $this->grav->fireEvent('onTwigPageVariables', new Event(['page' => $item])); + $twig_vars = $this->twig_vars; + + $twig_vars['page'] = $item; + $twig_vars['media'] = $item->media(); + $twig_vars['header'] = $item->header(); + + $local_twig = clone($this->twig); + $modular_twig = $item->modularTwig(); + $process_twig = isset($item->header()->process['twig']) ? $item->header()->process['twig'] : false; + + $output = ''; + + try { + // Process Modular Twig + if ($modular_twig) { + $twig_vars['content'] = $content; + $template = $item->template() . TEMPLATE_EXT; + $output = $content = $local_twig->render($template, $twig_vars); + } + + // Process in-page Twig + if (!$modular_twig || ($modular_twig && $process_twig)) { + $name = '@Page:' . $item->path(); + $this->setTemplate($name, $content); + $output = $local_twig->render($name, $twig_vars); + } + } catch (\Twig_Error_Loader $e) { + throw new \RuntimeException($e->getRawMessage(), 404, $e); + } + + return $output; + } + + /** + * Process a Twig template directly by using a template name + * and optional array of variables + * + * @param string $template template to render with + * @param array $vars Optional variables + * + * @return string + */ + public function processTemplate($template, $vars = []) + { + // override the twig header vars for local resolution + $this->grav->fireEvent('onTwigTemplateVariables'); + $vars += $this->twig_vars; + + try { + $output = $this->twig->render($template, $vars); + } catch (\Twig_Error_Loader $e) { + throw new \RuntimeException($e->getRawMessage(), 404, $e); + } + + return $output; + + } + + + /** + * Process a Twig template directly by using a Twig string + * and optional array of variables + * + * @param string $string string to render. + * @param array $vars Optional variables + * + * @return string + */ + public function processString($string, array $vars = []) + { + // override the twig header vars for local resolution + $this->grav->fireEvent('onTwigStringVariables'); + $vars += $this->twig_vars; + + $name = '@Var:' . $string; + $this->setTemplate($name, $string); + + try { + $output = $this->twig->render($name, $vars); + } catch (\Twig_Error_Loader $e) { + throw new \RuntimeException($e->getRawMessage(), 404, $e); + } + + return $output; + } + + /** + * Twig process that renders the site layout. This is the main twig process that renders the overall + * page and handles all the layout for the site display. + * + * @param string $format Output format (defaults to HTML). + * + * @return string the rendered output + * @throws \RuntimeException + */ + public function processSite($format = null) + { + // set the page now its been processed + $this->grav->fireEvent('onTwigSiteVariables'); + $pages = $this->grav['pages']; + $page = $this->grav['page']; + $content = $page->content(); + + $twig_vars = $this->twig_vars; + + $twig_vars['pages'] = $pages->root(); + $twig_vars['page'] = $page; + $twig_vars['header'] = $page->header(); + $twig_vars['media'] = $page->media(); + $twig_vars['content'] = $content; + $ext = '.' . ($format ? $format : 'html') . TWIG_EXT; + + // determine if params are set, if so disable twig cache + $params = $this->grav['uri']->params(null, true); + if (!empty($params)) { + $this->twig->setCache(false); + } + + // Get Twig template layout + $template = $this->template($page->template() . $ext); + + try { + $output = $this->twig->render($template, $twig_vars); + } catch (\Twig_Error_Loader $e) { + $error_msg = $e->getMessage(); + // Try html version of this template if initial template was NOT html + if ($ext != '.html' . TWIG_EXT) { + try { + $output = $this->twig->render($page->template() . '.html' . TWIG_EXT, $twig_vars); + } catch (\Twig_Error_Loader $e) { + throw new \RuntimeException($error_msg, 400, $e); + } + } else { + throw new \RuntimeException($error_msg, 400, $e); + } + } + + return $output; + } + + /** + * Simple helper method to get the twig template if it has already been set, else return + * the one being passed in + * + * @param string $template the template name + * + * @return string the template name + */ + public function template($template) + { + if (isset($this->template)) { + return $this->template; + } else { + return $template; + } + } +} diff --git a/system/src/Grav/Common/Twig/TwigEnvironment.php b/system/src/Grav/Common/Twig/TwigEnvironment.php new file mode 100644 index 0000000..af402ad --- /dev/null +++ b/system/src/Grav/Common/Twig/TwigEnvironment.php @@ -0,0 +1,14 @@ +grav = Grav::instance(); + $this->debugger = isset($this->grav['debugger']) ? $this->grav['debugger'] : null; + $this->config = $this->grav['config']; + } + + /** + * Returns extension name. + * + * @return string + */ + public function getName() + { + return 'GravTwigExtension'; + } + + /** + * Register some standard globals + * + * @return array + */ + public function getGlobals() + { + return [ + 'grav' => $this->grav, + ]; + } + + /** + * Return a list of all filters. + * + * @return array + */ + public function getFilters() + { + return [ + new \Twig_SimpleFilter('*ize', [$this, 'inflectorFilter']), + new \Twig_SimpleFilter('absolute_url', [$this, 'absoluteUrlFilter']), + new \Twig_SimpleFilter('contains', [$this, 'containsFilter']), + new \Twig_SimpleFilter('defined', [$this, 'definedDefaultFilter']), + new \Twig_SimpleFilter('ends_with', [$this, 'endsWithFilter']), + new \Twig_SimpleFilter('fieldName', [$this, 'fieldNameFilter']), + new \Twig_SimpleFilter('ksort', [$this, 'ksortFilter']), + new \Twig_SimpleFilter('ltrim', [$this, 'ltrimFilter']), + new \Twig_SimpleFilter('markdown', [$this, 'markdownFilter']), + new \Twig_SimpleFilter('md5', [$this, 'md5Filter']), + new \Twig_SimpleFilter('nicetime', [$this, 'nicetimeFilter']), + new \Twig_SimpleFilter('randomize', [$this, 'randomizeFilter']), + new \Twig_SimpleFilter('modulus', [$this, 'modulusFilter']), + new \Twig_SimpleFilter('rtrim', [$this, 'rtrimFilter']), + new \Twig_SimpleFilter('pad', [$this, 'padFilter']), + new \Twig_SimpleFilter('safe_email', [$this, 'safeEmailFilter']), + new \Twig_SimpleFilter('safe_truncate', ['\Grav\Common\Utils', 'safeTruncate']), + new \Twig_SimpleFilter('safe_truncate_html', ['\Grav\Common\Utils', 'safeTruncateHTML']), + new \Twig_SimpleFilter('sort_by_key', [$this, 'sortByKeyFilter']), + new \Twig_SimpleFilter('starts_with', [$this, 'startsWithFilter']), + new \Twig_SimpleFilter('t', [$this, 'translate']), + new \Twig_SimpleFilter('ta', [$this, 'translateArray']), + new \Twig_SimpleFilter('truncate', ['\Grav\Common\Utils', 'truncate']), + new \Twig_SimpleFilter('truncate_html', ['\Grav\Common\Utils', 'truncateHTML']), + ]; + } + + /** + * Return a list of all functions. + * + * @return array + */ + public function getFunctions() + { + return [ + new \Twig_SimpleFunction('array', [$this, 'arrayFunc']), + new \Twig_simpleFunction('authorize', [$this, 'authorize']), + new \Twig_SimpleFunction('debug', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]), + new \Twig_SimpleFunction('dump', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]), + new \Twig_SimpleFunction('evaluate', [$this, 'evaluateFunc']), + new \Twig_SimpleFunction('gist', [$this, 'gistFunc']), + new \Twig_SimpleFunction('nonce_field', [$this, 'nonceFieldFunc']), + new \Twig_simpleFunction('random_string', [$this, 'randomStringFunc']), + new \Twig_SimpleFunction('repeat', [$this, 'repeatFunc']), + new \Twig_SimpleFunction('string', [$this, 'stringFunc']), + new \Twig_simpleFunction('t', [$this, 'translate']), + new \Twig_simpleFunction('ta', [$this, 'translateArray']), + new \Twig_SimpleFunction('url', [$this, 'urlFunc']), + + + ]; + } + + /** + * Filters field name by changing dot notation into array notation. + * + * @param string $str + * + * @return string + */ + public function fieldNameFilter($str) + { + $path = explode('.', $str); + + return array_shift($path) . ($path ? '[' . implode('][', $path) . ']' : ''); + } + + /** + * Protects email address. + * + * @param string $str + * + * @return string + */ + public function safeEmailFilter($str) + { + $email = ''; + $str_len = strlen($str); + for ($i = 0; $i < $str_len; $i++) { + $email .= "&#" . ord($str[$i]) . ";"; + } + + return $email; + } + + /** + * Returns array in a random order. + * + * @param array $original + * @param int $offset Can be used to return only slice of the array. + * + * @return array + */ + public function randomizeFilter($original, $offset = 0) + { + if (!is_array($original)) { + return $original; + } + + if ($original instanceof \Traversable) { + $original = iterator_to_array($original, false); + } + + $sorted = []; + $random = array_slice($original, $offset); + shuffle($random); + + $sizeOf = sizeof($original); + for ($x = 0; $x < $sizeOf; $x++) { + if ($x < $offset) { + $sorted[] = $original[$x]; + } else { + $sorted[] = array_shift($random); + } + } + + return $sorted; + } + + /** + * Returns the modulus of an integer + * + * @param int $number + * @param int $divider + * @param array $items array of items to select from to return + * + * @return int + */ + public function modulusFilter($number, $divider, $items = null) + { + if (is_string($number)) { + $number = strlen($number); + } + + $remainder = $number % $divider; + + if (is_array($items)) { + if (isset($items[$remainder])) { + return $items[$remainder]; + } else { + return $items[0]; + } + } + + return $remainder; + } + + /** + * Inflector supports following notations: + * + * {{ 'person'|pluralize }} => people + * {{ 'shoes'|singularize }} => shoe + * {{ 'welcome page'|titleize }} => "Welcome Page" + * {{ 'send_email'|camelize }} => SendEmail + * {{ 'CamelCased'|underscorize }} => camel_cased + * {{ 'Something Text'|hyphenize }} => something-text + * {{ 'something_text_to_read'|humanize }} => "Something text to read" + * {{ '181'|monthize }} => 6 + * {{ '10'|ordinalize }} => 10th + * + * @param string $action + * @param string $data + * @param int $count + * + * @return mixed + */ + public function inflectorFilter($action, $data, $count = null) + { + $action = $action . 'ize'; + + $inflector = $this->grav['inflector']; + + if (in_array( + $action, + ['titleize', 'camelize', 'underscorize', 'hyphenize', 'humanize', 'ordinalize', 'monthize'] + )) { + return $inflector->$action($data); + } elseif (in_array($action, ['pluralize', 'singularize'])) { + if ($count) { + return $inflector->$action($data, $count); + } else { + return $inflector->$action($data); + } + } else { + return $data; + } + } + + /** + * Return MD5 hash from the input. + * + * @param string $str + * + * @return string + */ + public function md5Filter($str) + { + return md5($str); + } + + /** + * Sorts a collection by key + * + * @param array $input + * @param string $filter + * @param array|int $direction + * + * @return string + */ + public function sortByKeyFilter(array $input, $filter, $direction = SORT_ASC) + { + $output = []; + + if (!$input) { + return $output; + } + + foreach ($input as $key => $row) { + $output[$key] = $row[$filter]; + } + + array_multisort($output, $direction, $input); + + return $input; + } + + /** + * Return ksorted collection. + * + * @param array $array + * + * @return array + */ + public function ksortFilter(array $array) + { + ksort($array); + + return $array; + } + + /** + * determine if a string contains another + * + * @param String $haystack + * @param String $needle + * + * @return boolean + */ + public function containsFilter($haystack, $needle) + { + return (strpos($haystack, $needle) !== false); + } + + /** + * displays a facebook style 'time ago' formatted date/time + * + * @param $date + * @param $long_strings + * @param String + * + * @return boolean + */ + public function nicetimeFilter($date, $long_strings = true) + { + if (empty($date)) { + return $this->grav['language']->translate('NICETIME.NO_DATE_PROVIDED', null, true); + } + + if ($long_strings) { + $periods = [ + "NICETIME.SECOND", + "NICETIME.MINUTE", + "NICETIME.HOUR", + "NICETIME.DAY", + "NICETIME.WEEK", + "NICETIME.MONTH", + "NICETIME.YEAR", + "NICETIME.DECADE" + ]; + } else { + $periods = [ + "NICETIME.SEC", + "NICETIME.MIN", + "NICETIME.HR", + "NICETIME.DAY", + "NICETIME.WK", + "NICETIME.MO", + "NICETIME.YR", + "NICETIME.DEC" + ]; + } + + $lengths = ["60", "60", "24", "7", "4.35", "12", "10"]; + + $now = time(); + + // check if unix timestamp + if ((string)(int)$date == $date) { + $unix_date = $date; + } else { + $unix_date = strtotime($date); + } + + // check validity of date + if (empty($unix_date)) { + return $this->grav['language']->translate('NICETIME.BAD_DATE', null, true); + } + + // is it future date or past date + if ($now > $unix_date) { + $difference = $now - $unix_date; + $tense = $this->grav['language']->translate('NICETIME.AGO', null, true); + + } else { + $difference = $unix_date - $now; + $tense = $this->grav['language']->translate('NICETIME.FROM_NOW', null, true); + } + + for ($j = 0; $difference >= $lengths[$j] && $j < count($lengths) - 1; $j++) { + $difference /= $lengths[$j]; + } + + $difference = round($difference); + + if ($difference != 1) { + $periods[$j] .= '_PLURAL'; + } + + if ($this->grav['language']->getTranslation($this->grav['language']->getLanguage(), + $periods[$j] . '_MORE_THAN_TWO') + ) { + if ($difference > 2) { + $periods[$j] .= '_MORE_THAN_TWO'; + } + } + + $periods[$j] = $this->grav['language']->translate($periods[$j], null, true); + + return "$difference $periods[$j] {$tense}"; + } + + /** + * @param $string + * + * @return mixed + */ + public function absoluteUrlFilter($string) + { + $url = $this->grav['uri']->base(); + $string = preg_replace('/((?:href|src) *= *[\'"](?!(http|ftp)))/i', "$1$url", $string); + + return $string; + + } + + /** + * @param $string + * + * @return mixed|string + */ + public function markdownFilter($string) + { + $page = $this->grav['page']; + $defaults = $this->config->get('system.pages.markdown'); + + // Initialize the preferred variant of Parsedown + if ($defaults['extra']) { + $parsedown = new ParsedownExtra($page, $defaults); + } else { + $parsedown = new Parsedown($page, $defaults); + } + + $string = $parsedown->text($string); + + return $string; + } + + /** + * @param $haystack + * @param $needle + * + * @return bool + */ + public function startsWithFilter($haystack, $needle) + { + return Utils::startsWith($haystack, $needle); + } + + /** + * @param $haystack + * @param $needle + * + * @return bool + */ + public function endsWithFilter($haystack, $needle) + { + return Utils::endsWith($haystack, $needle); + } + + /** + * @param $value + * @param null $default + * + * @return null + */ + public function definedDefaultFilter($value, $default = null) + { + if (isset($value)) { + return $value; + } else { + return $default; + } + } + + /** + * @param $value + * @param null $chars + * + * @return string + */ + public function rtrimFilter($value, $chars = null) + { + return rtrim($value, $chars); + } + + /** + * @param $value + * @param null $chars + * + * @return string + */ + public function ltrimFilter($value, $chars = null) + { + return ltrim($value, $chars); + } + + /** + * @return mixed + */ + public function translate() + { + return $this->grav['language']->translate(func_get_args()); + } + + /** + * @param $key + * @param $index + * @param null $lang + * + * @return mixed + */ + public function translateArray($key, $index, $lang = null) + { + return $this->grav['language']->translateArray($key, $index, $lang); + } + + /** + * Repeat given string x times. + * + * @param string $input + * @param int $multiplier + * + * @return string + */ + public function repeatFunc($input, $multiplier) + { + return str_repeat($input, $multiplier); + } + + /** + * Return URL to the resource. + * + * @example {{ url('theme://images/logo.png')|default('http://www.placehold.it/150x100/f4f4f4') }} + * + * @param string $input Resource to be located. + * @param bool $domain True to include domain name. + * + * @return string|null Returns url to the resource or null if resource was not found. + */ + public function urlFunc($input, $domain = false) + { + if (!trim((string)$input)) { + return false; + } + + if ($this->grav['config']->get('system.absolute_urls', false)) { + $domain = true; + } + + + if (strpos((string)$input, '://')) { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + + // Get relative path to the resource (or false if not found). + $resource = $locator->findResource((string)$input, false); + } else { + $resource = (string)$input; + } + + /** @var Uri $uri */ + $uri = $this->grav['uri']; + + return $resource ? rtrim($uri->rootUrl($domain), '/') . '/' . $resource : null; + } + + /** + * Evaluate a string + * + * @example {{ evaluate('grav.language.getLanguage') }} + * + * @param string $input String to be evaluated + * + * @return string Returns the evaluated string + */ + public function evaluateFunc($input) + { + return $this->grav['twig']->processString("{{ $input }}"); + } + + /** + * Based on Twig_Extension_Debug / twig_var_dump + * (c) 2011 Fabien Potencier + * + * @param \Twig_Environment $env + * @param $context + */ + public function dump(\Twig_Environment $env, $context) + { + if (!$env->isDebug() || !$this->debugger) { + return; + } + + $count = func_num_args(); + if (2 === $count) { + $data = []; + foreach ($context as $key => $value) { + if (is_object($value)) { + if (method_exists($value, 'toArray')) { + $data[$key] = $value->toArray(); + } else { + $data[$key] = "Object (" . get_class($value) . ")"; + } + } else { + $data[$key] = $value; + } + } + $this->debugger->addMessage($data, 'debug'); + } else { + for ($i = 2; $i < $count; $i++) { + $this->debugger->addMessage(func_get_arg($i), 'debug'); + } + } + } + + /** + * Output a Gist + * + * @param string $id + * + * @return string + */ + public function gistFunc($id) + { + return ''; + } + + /** + * Generate a random string + * + * @param int $count + * + * @return string + */ + public function randomStringFunc($count = 5) + { + return Utils::generateRandomString($count); + } + + /** + * Pad a string to a certain length with another string + * + * @param $input + * @param $pad_length + * @param string $pad_string + * @param int $pad_type + * + * @return string + */ + public static function padFilter($input, $pad_length, $pad_string = " ", $pad_type = STR_PAD_RIGHT) + { + return str_pad($input, (int)$pad_length, $pad_string, $pad_type); + } + + + /** + * Cast a value to array + * + * @param $value + * + * @return array + */ + public function arrayFunc($value) + { + return (array)$value; + } + + /** + * Returns a string from a value. If the value is array, return it json encoded + * + * @param $value + * + * @return string + */ + public function stringFunc($value) + { + if (is_array($value)) { //format the array as a string + return json_encode($value); + } else { + return $value; + } + } + + /** + * Translate a string + * + * @return string + */ + public function translateFunc() + { + return $this->grav['language']->translate(func_get_args()); + } + + /** + * Authorize an action. Returns true if the user is logged in and has the right to execute $action. + * + * @param string $action + * + * @return bool + */ + public function authorize($action) + { + if (!$this->grav['user']->authenticated) { + return false; + } + + $action = (array)$action; + + foreach ($action as $a) { + if ($this->grav['user']->authorize($a)) { + return true; + } + } + + return false; + } + + /** + * Used to add a nonce to a form. Call {{ nonce_field('action') }} specifying a string representing the action. + * + * For maximum protection, ensure that the string representing the action is as specific as possible + * + * @param string $action the action + * @param string $nonceParamName a custom nonce param name + * + * @return string the nonce input field + */ + public function nonceFieldFunc($action, $nonceParamName = 'nonce') + { + $string = ''; + + return $string; + } +} diff --git a/system/src/Grav/Common/Twig/WriteCacheFileTrait.php b/system/src/Grav/Common/Twig/WriteCacheFileTrait.php new file mode 100644 index 0000000..2c9def3 --- /dev/null +++ b/system/src/Grav/Common/Twig/WriteCacheFileTrait.php @@ -0,0 +1,44 @@ +get('system.twig.umask_fix', false); + } + + if (self::$umask) { + if (!is_dir(dirname($file))) { + $old = umask(0002); + mkdir(dirname($file), 0777, true); + umask($old); + } + parent::writeCacheFile($file, $content); + chmod($file, 0775); + } else { + parent::writeCacheFile($file, $content); + } + } +} diff --git a/system/src/Grav/Common/Uri.php b/system/src/Grav/Common/Uri.php new file mode 100644 index 0000000..4a70064 --- /dev/null +++ b/system/src/Grav/Common/Uri.php @@ -0,0 +1,1035 @@ +paths = []; + $this->params = []; + $this->query = []; + $this->name = $this->buildHostname(); + $this->env = $this->buildEnvironment(); + $this->port = $this->buildPort(); + $this->uri = $this->buildUri(); + $this->scheme = $this->buildScheme(); + $this->base = $this->buildBaseUrl(); + $this->host = $this->buildHost(); + $this->root_path = $this->buildRootPath(); + $this->root = $this->base . $this->root_path; + $this->url = $this->base . $this->uri; + } + + /** + * Return the hostname from $_SERVER, validated and without port + * + * @return string + */ + private function buildHostname() + { + $hostname = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost'); + + // Remove port from HTTP_HOST generated $hostname + $hostname = Utils::substrToString($hostname, ':'); + + // Validate the hostname + $hostname = $this->validateHostname($hostname) ? $hostname : 'unknown'; + + return $hostname; + } + + /** + * Validate a hostname + * + * @param string $hostname The hostname + * + * @return boolean + */ + public function validateHostname($hostname) + { + return (bool)preg_match(Uri::HOSTNAME_REGEX, $hostname); + } + + /** + * Get the port from $_SERVER + * + * @return string + */ + private function buildPort() + { + $port = isset($_SERVER['SERVER_PORT']) ? (string)$_SERVER['SERVER_PORT'] : '80'; + + return $port; + } + + /** + * Get the Uri from $_SERVER + * + * @return string + */ + private function buildUri() + { + $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; + + return $uri; + } + + private function buildScheme() + { + // set the base + if (isset($_SERVER['HTTPS'])) { + $scheme = (strtolower(@$_SERVER['HTTPS']) == 'on') ? 'https://' : 'http://'; + } else { + $scheme = 'http://'; + } + + return $scheme; + } + + /** + * Get the base URI with port if needed + * + * @return string + */ + private function buildBaseUrl() + { + return $this->scheme . $this->name; + } + + /** + * Get the Grav Root Path + * + * @return string + */ + private function buildRootPath() + { + $root_path = str_replace(' ', '%20', rtrim(substr($_SERVER['PHP_SELF'], 0, strpos($_SERVER['PHP_SELF'], 'index.php')), '/')); + + // check if userdir in the path and workaround PHP bug with PHP_SELF + if (strpos($this->uri, '/~') !== false && strpos($_SERVER['PHP_SELF'], '/~') === false) { + $root_path = substr($this->uri, 0, strpos($this->uri, '/', 1)) . $root_path; + } + + return $root_path; + } + + /** + * Returns the hostname + * + * @return string + */ + private function buildHost() + { + return $this->name; + } + + private function buildEnvironment() + { + // set hostname + $address = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '::1'; + + // check for localhost variations + if ($this->name == 'localhost' || $address == '::1' || $address == '127.0.0.1') { + $env = 'localhost'; + } else { + $env = $this->name; + } + + return $env; + } + + /** + * Initialize the URI class with a url passed via parameter. + * Used for testing purposes. + * + * @param string $url the URL to use in the class + * + * @return string + */ + public function initializeWithUrl($url = '') + { + if (!$url) { + return $this; + } + + $this->paths = []; + $this->params = []; + $this->query = []; + $this->name = []; + $this->env = []; + $this->port = []; + $this->uri = []; + $this->base = []; + $this->host = []; + $this->root = []; + $this->url = []; + + $grav = Grav::instance(); + + $language = $grav['language']; + + $uri_bits = Uri::parseUrl($url); + + $this->name = $uri_bits['host']; + $this->port = isset($uri_bits['port']) ? $uri_bits['port'] : '80'; + + $this->uri = $uri_bits['path']; + + // set active language + $uri = $language->setActiveFromUri($this->uri); + + if (isset($uri_bits['params'])) { + $this->params = $uri_bits['params']; + } + + if (isset($uri_bits['query'])) { + $this->uri .= '?' . $uri_bits['query']; + parse_str($uri_bits['query'], $this->query); + } + + $this->base = $this->buildBaseUrl(); + $this->host = $this->buildHost(); + $this->env = $this->buildEnvironment(); + $this->root_path = $this->buildRootPath(); + $this->root = $this->base . $this->root_path; + $this->url = $this->root . $uri; + $this->path = $uri; + + return $this; + } + + /** + * Initialize the URI class by providing url and root_path arguments + * + * @param string $url + * @param string $root_path + * + * @return $this + */ + public function initializeWithUrlAndRootPath($url, $root_path) + { + $this->initializeWithUrl($url); + $this->root_path = $root_path; + + return $this; + } + + /** + * Initializes the URI object based on the url set on the object + */ + public function init() + { + $grav = Grav::instance(); + + $config = $grav['config']; + $language = $grav['language']; + + // add the port to the base for non-standard ports + if ($config->get('system.reverse_proxy_setup') === false && $this->port != '80' && $this->port != '443') { + $this->base .= ":" . $this->port; + } + + // Set some defaults + $this->root = $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) { + $uri = str_replace($setup_base, '', $uri); + } + + // 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); + } + + // process params + $uri = $this->processParams($uri, $config->get('system.param_sep')); + + // set active language + $uri = $language->setActiveFromUri($uri); + + // split the URL and params + $bits = parse_url($uri); + + // process query string + if (isset($bits['query']) && isset($bits['path'])) { + if (!$this->query) { + $this->query = filter_input_array(INPUT_GET, FILTER_SANITIZE_STRING); + } + $uri = $bits['path']; + } + + // remove the extension if there is one set + $parts = pathinfo($uri); + + // set the original basename + $this->basename = $parts['basename']; + + // set the extension + if (isset($parts['extension'])) { + $this->extension = $parts['extension']; + } + + $valid_page_types = implode('|', $config->get('system.pages.types')); + + // Strip the file extension for valid page types + if (preg_match("/\.(" . $valid_page_types . ")$/", $parts['basename'])) { + $uri = rtrim(str_replace(DIRECTORY_SEPARATOR, DS, $parts['dirname']), DS) . '/' . $parts['filename']; + } + + // set the new url + $this->url = $this->root . $uri; + $this->path = $uri; + $this->content_path = trim(str_replace($this->base, '', $this->path), '/'); + if ($this->content_path != '') { + $this->paths = explode('/', $this->content_path); + } + + // Set some Grav stuff + $grav['base_url_absolute'] = $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']; + } + + /** + * Process any params based in this URL, supports any valid delimiter + * + * @param $uri + * @param string $delimiter + * + * @return string + */ + private function processParams($uri, $delimiter = ':') + { + if (strpos($uri, $delimiter) !== false) { + $bits = explode('/', $uri); + $path = []; + foreach ($bits as $bit) { + if (strpos($bit, $delimiter) !== false) { + $param = explode($delimiter, $bit); + if (count($param) == 2) { + $plain_var = filter_var(rawurldecode($param[1]), FILTER_SANITIZE_STRING); + $this->params[$param[0]] = $plain_var; + } + } else { + $path[] = $bit; + } + } + $uri = '/' . ltrim(implode('/', $path), '/'); + } + + return $uri; + } + + /** + * Return URI path. + * + * @param string $id + * + * @return string + */ + public function paths($id = null) + { + if (isset($id)) { + return $this->paths[$id]; + } else { + return $this->paths; + } + } + + /** + * Return route to the current URI. By default route doesn't include base path. + * + * @param bool $absolute True to include full path. + * @param bool $domain True to include domain. Works only if first parameter is also true. + * + * @return string + */ + public function route($absolute = false, $domain = false) + { + return rawurldecode(($absolute ? $this->rootUrl($domain) : '') . '/' . implode('/', $this->paths)); + } + + /** + * Return full query string or a single query attribute. + * + * @param string $id Optional attribute. Get a single query attribute if set + * @param bool $raw If true and $id is not set, return the full query array. Otherwise return the query string + * + * @return string|array Returns an array if $id = null and $raw = true + */ + public function query($id = null, $raw = false) + { + if (isset($id)) { + return isset($this->query[$id]) ? $this->query[$id] : null; + } else { + if ($raw) { + return $this->query; + } else { + if (!$this->query) { + return ''; + } + + return http_build_query($this->query); + } + } + } + + /** + * Return all or a single query parameter as a URI compatible string. + * + * @param string $id Optional parameter name. + * @param boolean $array return the array format or not + * + * @return null|string + */ + public function params($id = null, $array = false) + { + $config = Grav::instance()['config']; + + $params = null; + if ($id === null) { + if ($array) { + return $this->params; + } + $output = []; + foreach ($this->params as $key => $value) { + $output[] = $key . $config->get('system.param_sep') . $value; + $params = '/' . implode('/', $output); + } + } elseif (isset($this->params[$id])) { + if ($array) { + return $this->params[$id]; + } + $params = "/{$id}" . $config->get('system.param_sep') . $this->params[$id]; + } + + return $params; + } + + /** + * Get URI parameter. + * + * @param string $id + * + * @return bool|string + */ + public function param($id) + { + if (isset($this->params[$id])) { + return rawurldecode($this->params[$id]); + } else { + return false; + } + } + + /** + * Return URL. + * + * @param bool $include_host Include hostname. + * + * @return string + */ + public function url($include_host = false) + { + if ($include_host) { + return $this->url; + } else { + $url = (str_replace($this->base, '', rtrim($this->url, '/'))); + + return $url ? $url : '/'; + } + } + + /** + * Return the Path + * + * @return String The path of the URI + */ + public function path() + { + $path = $this->path; + if ($path === '') { + $path = '/'; + } + + return $path; + } + + /** + * Return the Extension of the URI + * + * @param null $default + * + * @return String The extension of the URI + */ + public function extension($default = null) + { + if (!$this->extension) { + $this->extension = $default; + } + + return $this->extension; + } + + /** + * Return the scheme of the URI + * + * @return String The scheme of the URI + */ + public function scheme() + { + return $this->scheme; + } + + + /** + * Return the host of the URI + * + * @return String The host of the URI + */ + public function host() + { + return $this->host; + } + + /** + * Return the port number + * + * @return int + */ + public function port() + { + return $this->port; + } + + /** + * Gets the environment name + * + * @return String + */ + public function environment() + { + return $this->env; + } + + + /** + * Return the basename of the URI + * + * @return String The basename of the URI + */ + public function basename() + { + return $this->basename; + } + + /** + * Return the base of the URI + * + * @return String The base of the URI + */ + public function base() + { + return $this->base; + } + + /** + * Return root URL to the site. + * + * @param bool $include_host Include hostname. + * + * @return mixed + */ + public function rootUrl($include_host = false) + { + if ($include_host) { + return $this->root; + } else { + $root = str_replace($this->base, '', $this->root); + + return $root; + } + } + + /** + * Return current page number. + * + * @return int + */ + public function currentPage() + { + if (isset($this->params['page'])) { + return $this->params['page']; + } else { + return 1; + } + } + + /** + * Return relative path to the referrer defaulting to current or given page. + * + * @param string $default + * @param string $attributes + * + * @return string + */ + public function referrer($default = null, $attributes = null) + { + $referrer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null; + + // Check that referrer came from our site. + $root = $this->rootUrl(true); + if ($referrer) { + // Referrer should always have host set and it should come from the same base address. + if (stripos($referrer, $root) !== 0) { + $referrer = null; + } + } + + if (!$referrer) { + $referrer = $default ? $default : $this->route(true, true); + } + + if ($attributes) { + $referrer .= $attributes; + } + + // Return relative path. + return substr($referrer, strlen($root)); + } + + /** + * Return the IP address of the current user + * + * @return string ip address + */ + public function ip() + { + if (getenv('HTTP_CLIENT_IP')) + $ipaddress = getenv('HTTP_CLIENT_IP'); + else if(getenv('HTTP_X_FORWARDED_FOR')) + $ipaddress = getenv('HTTP_X_FORWARDED_FOR'); + else if(getenv('HTTP_X_FORWARDED')) + $ipaddress = getenv('HTTP_X_FORWARDED'); + else if(getenv('HTTP_FORWARDED_FOR')) + $ipaddress = getenv('HTTP_FORWARDED_FOR'); + else if(getenv('HTTP_FORWARDED')) + $ipaddress = getenv('HTTP_FORWARDED'); + else if(getenv('REMOTE_ADDR')) + $ipaddress = getenv('REMOTE_ADDR'); + else + $ipaddress = 'UNKNOWN'; + return $ipaddress; + + } + + /** + * Is this an external URL? if it starts with `http` then yes, else false + * + * @param string $url the URL in question + * + * @return boolean is eternal state + */ + public function isExternal($url) + { + if (Utils::startsWith($url, 'http')) { + return true; + } else { + return false; + } + } + + /** + * The opposite of built-in PHP method parse_url() + * + * @param $parsed_url + * + * @return string + */ + public static function buildUrl($parsed_url) + { + $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : ''; + $host = isset($parsed_url['host']) ? $parsed_url['host'] : ''; + $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; + $user = isset($parsed_url['user']) ? $parsed_url['user'] : ''; + $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : ''; + $pass = ($user || $pass) ? "$pass@" : ''; + $path = isset($parsed_url['path']) ? $parsed_url['path'] : ''; + $params = isset($parsed_url['params']) ? static::buildParams($parsed_url['params']) : ''; + $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; + $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; + + return "$scheme$user$pass$host$port$path$params$query$fragment"; + } + + public static function buildParams($params) + { + $grav = Grav::instance(); + + $params_string = ''; + foreach ($params as $key => $value) { + $output[] = $key . $grav['config']->get('system.param_sep') . $value; + $params_string .= '/' . implode('/', $output); + } + return $params_string; + } + + /** + * Converts links from absolute '/' or relative (../..) to a Grav friendly format + * + * @param Page $page the current page to use as reference + * @param string $url the URL as it was written in the markdown + * @param string $type the type of URL, image | link + * @param null $absolute if null, will use system default, if true will use absolute links internally + * + * @return string the more friendly formatted url + */ + public static function convertUrl(Page $page, $url, $type = 'link', $absolute = false) + { + $grav = Grav::instance(); + + $uri = $grav['uri']; + + // Link processing should prepend language + $language = $grav['language']; + $language_append = ''; + if ($type == 'link' && $language->enabled()) { + $language_append = $language->getLanguageURLPrefix(); + } + + // Handle Excerpt style $url array + if (is_array($url)) { + $url_path = $url['path']; + } else { + $url_path = $url; + } + + $external = false; + $base = $grav['base_url_relative']; + $base_url = rtrim($base . $grav['pages']->base(), '/') . $language_append; + $pages_dir = $grav['locator']->findResource('page://'); + + // if absolute and starts with a base_url move on + if (isset($url['scheme']) && Utils::startsWith($url['scheme'], 'http')) { + $external = true; + } elseif (($base_url != '' && Utils::startsWith($url_path, $base_url)) || + $url_path == '/' || + Utils::startsWith($url_path, '#')) { + $url_path = $base_url . $url_path; + } else { + + // see if page is relative to this or absolute + if (Utils::startsWith($url_path, '/')) { + $normalized_url = Utils::normalizePath($base_url . $url_path); + $normalized_path = Utils::normalizePath($pages_dir . $url_path); + } else { + $page_route = ($page->home() && !empty($url_path)) ? $page->rawRoute() : $page->route(); + $normalized_url = $base_url . Utils::normalizePath($page_route . '/' . $url_path); + $normalized_path = Utils::normalizePath($page->path() . '/' . $url_path); + } + + // special check to see if path checking is required. + $just_path = str_replace($normalized_url, '', $normalized_path); + if ($just_path == $page->path() || $normalized_url == '/') { + $url_path = $normalized_url; + } else { + $url_bits = static::parseUrl($normalized_path); + $full_path = ($url_bits['path']); + $raw_full_path = rawurldecode($full_path); + + if (file_exists($raw_full_path)) { + $full_path = $raw_full_path; + } elseif (file_exists($full_path)) { + // do nothing + } else { + $full_path = false; + } + + if ($full_path) { + $path_info = pathinfo($full_path); + $page_path = $path_info['dirname']; + $filename = ''; + + if ($url_path == '..') { + $page_path = $full_path; + } else { + // save the filename if a file is part of the path + if (is_file($full_path)) { + if ($path_info['extension'] != 'md') { + $filename = '/' . $path_info['basename']; + } + } else { + $page_path = $full_path; + } + } + + // get page instances and try to find one that fits + $instances = $grav['pages']->instances(); + if (isset($instances[$page_path])) { + /** @var Page $target */ + $target = $instances[$page_path]; + $url_bits['path'] = $base_url . rtrim($target->route(), '/') . $filename; + + $url_path = Uri::buildUrl($url_bits); + } else { + $url_path = $normalized_url; + } + } else { + $url_path = $normalized_url; + } + } + } + + // handle absolute URLs + if (!$external && ($absolute === true || $grav['config']->get('system.absolute_urls', false))) { + + $url['scheme'] = str_replace('://', '', $uri->scheme()); + $url['host'] = $uri->host(); + + if ($uri->port() != 80 && $uri->port() != 443) { + $url['port'] = $uri->port(); + } + + // check if page exists for this route, and if so, check if it has SSL enabled + $pages = $grav['pages']; + $routes = $pages->routes(); + + // if this is an image, get the proper path + $url_bits = pathinfo($url_path); + if (isset($url_bits['extension'])) { + $target_path = $url_bits['dirname']; + } else { + $target_path = $url_path; + } + + // strip base from this path + $target_path = str_replace($uri->rootUrl(), '', $target_path); + + // set to / if root + if (empty($target_path)) { + $target_path = '/'; + } + + // look to see if this page exists and has ssl enabled + if (isset($routes[$target_path])) { + $target_page = $pages->get($routes[$target_path]); + if ($target_page) { + $ssl_enabled = $target_page->ssl(); + if (isset($ssl_enabled)) { + if ($ssl_enabled) { + $url['scheme'] = 'https'; + } else { + $url['scheme'] = 'http'; + } + } + } + } + } + + // transform back to string/array as needed + if (is_array($url)) { + $url['path'] = $url_path; + } else { + $url = $url_path; + } + + return $url; + } + + public static function parseUrl($url) + { + $bits = parse_url($url); + + $grav = Grav::instance(); + + list($stripped_path, $params) = static::extractParams($bits['path'], $grav['config']->get('system.param_sep')); + + if (!empty($params)) { + $bits['path'] = $stripped_path; + $bits['params'] = $params; + } + + return $bits; + } + + public static function extractParams($uri, $delimiter) + { + $params = []; + + if (strpos($uri, $delimiter) !== false) { + $bits = explode('/', $uri); + $path = []; + foreach ($bits as $bit) { + if (strpos($bit, $delimiter) !== false) { + $param = explode($delimiter, $bit); + if (count($param) == 2) { + $plain_var = filter_var(rawurldecode($param[1]), FILTER_SANITIZE_STRING); + $params[$param[0]] = $plain_var; + } + } else { + $path[] = $bit; + } + } + $uri = '/' . ltrim(implode('/', $path), '/'); + } + + return [$uri, $params]; + } + + /** + * Converts links from absolute '/' or relative (../..) to a Grav friendly format + * + * @param Page $page the current page to use as reference + * @param string $markdown_url the URL as it was written in the markdown + * @param string $type the type of URL, image | link + * @param null $relative if null, will use system default, if true will use relative links internally + * + * @return string the more friendly formatted url + */ + public static function convertUrlOld(Page $page, $markdown_url, $type = 'link', $relative = null) + { + $grav = Grav::instance(); + + $language = $grav['language']; + + // Link processing should prepend language + $language_append = ''; + if ($type == 'link' && $language->enabled()) { + $language_append = $language->getLanguageURLPrefix(); + } + + $pages_dir = $grav['locator']->findResource('page://'); + if (is_null($relative)) { + $base = $grav['base_url']; + } else { + $base = $relative ? $grav['base_url_relative'] : $grav['base_url_absolute']; + } + + $base_url = rtrim($base . $grav['pages']->base(), '/') . $language_append; + + // if absolute and starts with a base_url move on + if (pathinfo($markdown_url, PATHINFO_DIRNAME) == '.' && $page->url() == '/') { + return '/' . $markdown_url; + // no path to convert + } elseif ($base_url != '' && Utils::startsWith($markdown_url, $base_url)) { + return $markdown_url; + // if contains only a fragment + } elseif (Utils::startsWith($markdown_url, '#')) { + return $markdown_url; + } else { + $target = null; + // see if page is relative to this or absolute + if (Utils::startsWith($markdown_url, '/')) { + $normalized_url = Utils::normalizePath($base_url . $markdown_url); + $normalized_path = Utils::normalizePath($pages_dir . $markdown_url); + } else { + $normalized_url = $base_url . Utils::normalizePath($page->route() . '/' . $markdown_url); + $normalized_path = Utils::normalizePath($page->path() . '/' . $markdown_url); + } + + // special check to see if path checking is required. + $just_path = str_replace($normalized_url, '', $normalized_path); + if ($just_path == $page->path()) { + return $normalized_url; + } + + $url_bits = parse_url($normalized_path); + $full_path = ($url_bits['path']); + + if (file_exists($full_path)) { + // do nothing + } elseif (file_exists(rawurldecode($full_path))) { + $full_path = rawurldecode($full_path); + } else { + return $normalized_url; + } + + $path_info = pathinfo($full_path); + $page_path = $path_info['dirname']; + $filename = ''; + + if ($markdown_url == '..') { + $page_path = $full_path; + } else { + // save the filename if a file is part of the path + if (is_file($full_path)) { + if ($path_info['extension'] != 'md') { + $filename = '/' . $path_info['basename']; + } + } else { + $page_path = $full_path; + } + } + + // get page instances and try to find one that fits + $instances = $grav['pages']->instances(); + if (isset($instances[$page_path])) { + /** @var Page $target */ + $target = $instances[$page_path]; + $url_bits['path'] = $base_url . rtrim($target->route(), '/') . $filename; + + return Uri::buildUrl($url_bits); + } + + return $normalized_url; + } + } + + /** + * Adds the nonce to a URL for a specific action + * + * @param string $url the url + * @param string $action the action + * @param string $nonceParamName the param name to use + * + * @return string the url with the nonce + */ + public static function addNonce($url, $action, $nonceParamName = 'nonce') + { + $urlWithNonce = $url . '/' . $nonceParamName . Grav::instance()['config']->get('system.param_sep', ':') . Utils::getNonce($action); + + return $urlWithNonce; + } +} diff --git a/system/src/Grav/Common/User/Authentication.php b/system/src/Grav/Common/User/Authentication.php new file mode 100644 index 0000000..4d36603 --- /dev/null +++ b/system/src/Grav/Common/User/Authentication.php @@ -0,0 +1,53 @@ +get('groups'); + + return $groups; + } + + /** + * Checks if a group exists + * + * @param string $groupname + * + * @return object + */ + public static function groupExists($groupname) + { + return isset(self::groups()[$groupname]); + } + + /** + * Get a group by name + * + * @param string $groupname + * + * @return object + */ + public static function load($groupname) + { + if (self::groupExists($groupname)) { + $content = self::groups()[$groupname]; + } else { + $content = []; + } + + $blueprints = new Blueprints('blueprints://'); + $blueprint = $blueprints->get('user/group'); + if (!isset($content['groupname'])) { + $content['groupname'] = $groupname; + } + $group = new Group($content, $blueprint); + + return $group; + } + + /** + * Save a group + */ + public function save() + { + $blueprints = new Blueprints('blueprints://'); + $blueprint = $blueprints->get('user/group'); + + $fields = $blueprint->fields(); + + self::getGrav()['config']->set("groups.$this->groupname", []); + + foreach ($fields as $field) { + if ($field['type'] == 'text') { + $value = $field['name']; + if (isset($this->items[$value])) { + self::getGrav()['config']->set("groups.$this->groupname.$value", $this->items[$value]); + } + } + if ($field['type'] == 'array') { + $value = $field['name']; + $arrayValues = Utils::resolve($this->items, $field['name']); + + if ($arrayValues) { + foreach ($arrayValues as $arrayIndex => $arrayValue) { + self::getGrav()['config']->set("groups.$this->groupname.$value.$arrayIndex", $arrayValue); + } + } + } + } + + $type = 'groups'; + $blueprints = $this->blueprints("config/{$type}"); + $obj = new Data(self::getGrav()['config']->get($type), $blueprints); + $file = CompiledYamlFile::instance(self::getGrav()['locator']->findResource("config://{$type}.yaml")); + $obj->file($file); + $obj->save(); + } + + /** + * Remove a group + * + * @param string $groupname + * + * @return bool True if the action was performed + */ + public static function remove($groupname) + { + $blueprints = new Blueprints('blueprints://'); + $blueprint = $blueprints->get('user/group'); + + $groups = self::getGrav()['config']->get("groups"); + unset($groups[$groupname]); + self::getGrav()['config']->set("groups", $groups); + + $type = 'groups'; + $obj = new Data(self::getGrav()['config']->get($type), $blueprint); + $file = CompiledYamlFile::instance(self::getGrav()['locator']->findResource("config://{$type}.yaml")); + $obj->file($file); + $obj->save(); + + return true; + } +} diff --git a/system/src/Grav/Common/User/User.php b/system/src/Grav/Common/User/User.php new file mode 100644 index 0000000..da51c12 --- /dev/null +++ b/system/src/Grav/Common/User/User.php @@ -0,0 +1,198 @@ +exists(). + * + * @param string $username + * + * @return User + */ + public static function load($username) + { + $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); + $file = CompiledYamlFile::instance($file_path); + $content = $file->content(); + if (!isset($content['username'])) { + $content['username'] = $username; + } + if (!isset($content['state'])) { + $content['state'] = 'enabled'; + } + $user = new User($content, $blueprint); + $user->file($file); + + return $user; + } + + /** + * Remove user account. + * + * @param string $username + * + * @return bool True if the action was performed + */ + public static function remove($username) + { + $file_path = self::getGrav()['locator']->findResource('account://' . $username . YAML_EXT); + if (file_exists($file_path) && unlink($file_path)) { + return true; + } + + return false; + } + + /** + * Authenticate user. + * + * If user password needs to be updated, new information will be saved. + * + * @param string $password Plaintext password. + * + * @return bool + */ + public function authenticate($password) + { + $save = false; + + // Plain-text is still stored + if ($this->password) { + if ($password !== $this->password) { + // Plain-text passwords do not match, we know we should fail but execute + // verify to protect us from timing attacks and return false regardless of + // the result + Authentication::verify( + $password, + self::getGrav()['config']->get('system.security.default_hash') + ); + + return false; + } else { + // Plain-text does match, we can update the hash and proceed + $save = true; + + $this->hashed_password = Authentication::create($this->password); + unset($this->password); + } + + } + + $result = Authentication::verify($password, $this->hashed_password); + + // Password needs to be updated, save the file. + if ($result == 2) { + $save = true; + $this->hashed_password = Authentication::create($password); + } + + if ($save) { + $this->save(); + } + + return (bool)$result; + } + + /** + * Save user without the username + */ + public function save() + { + $file = $this->file(); + if ($file) { + // if plain text password, hash it and remove plain text + if ($this->password) { + $this->hashed_password = Authentication::create($this->password); + unset($this->password); + } + + $username = $this->get('username'); + unset($this->username); + $file->save($this->items); + $this->set('username', $username); + } + } + + /** + * Checks user authorization to the action. + * + * @param string $action + * + * @return bool + */ + public function authorize($action) + { + if (empty($this->items)) { + return false; + } + + if (isset($this->state) && $this->state !== 'enabled') { + return false; + } + + $return = false; + + //Check group access level + $groups = $this->get('groups'); + if ($groups) { + foreach ((array)$groups as $group) { + $permission = self::getGrav()['config']->get("groups.{$group}.access.{$action}"); + $return = Utils::isPositive($permission); + if ($return === true) { + break; + } + } + } + + //Check user access level + if ($this->get('access')) { + if (Utils::resolve($this->get('access'), $action) !== null) { + $permission = $this->get("access.{$action}"); + $return = Utils::isPositive($permission); + } + } + + return $return; + } + + /** + * Checks user authorization to the action. + * Ensures backwards compatibility + * + * @param string $action + * + * @deprecated use authorize() + * @return bool + */ + public function authorise($action) + { + return $this->authorize($action); + } +} diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php new file mode 100644 index 0000000..85702c3 --- /dev/null +++ b/system/src/Grav/Common/Utils.php @@ -0,0 +1,619 @@ + 'd-m-Y H:i (e.g. '.$now->format('d-m-Y H:i').')', + 'Y-m-d H:i' => 'Y-m-d H:i (e.g. '.$now->format('Y-m-d H:i').')', + 'm/d/Y h:i a' => 'm/d/Y h:i (e.g. '.$now->format('m/d/Y h:i a').')', + 'H:i d-m-Y' => 'H:i d-m-Y (e.g. '.$now->format('H:i d-m-Y').')', + 'h:i a m/d/Y' => 'h:i a m/d/Y (e.g. '.$now->format('h:i a m/d/Y').')', + ]; + $default_format = self::getGrav()['config']->get('system.pages.dateformat.default'); + if ($default_format) { + $date_formats = array_merge([$default_format => $default_format.' (e.g. '.$now->format($default_format).')'], $date_formats); + } + + return $date_formats; + } + + /** + * Truncate text by number of characters but can cut off words. + * + * @param string $string + * @param int $limit Max number of characters. + * @param bool $up_to_break truncate up to breakpoint after char count + * @param string $break Break point. + * @param string $pad Appended padding to the end of the string. + * + * @return string + */ + public static function truncate($string, $limit = 150, $up_to_break = false, $break = " ", $pad = "…") + { + // return with no change if string is shorter than $limit + if (mb_strlen($string) <= $limit) { + return $string; + } + + // is $break present between $limit and the end of the string? + if ($up_to_break && false !== ($breakpoint = mb_strpos($string, $break, $limit))) { + if ($breakpoint < mb_strlen($string) - 1) { + $string = mb_substr($string, 0, $breakpoint) . $break; + } + } else { + $string = mb_substr($string, 0, $limit) . $pad; + } + + return $string; + } + + /** + * Truncate text by number of characters in a "word-safe" manor. + * + * @param string $string + * @param int $limit + * + * @return string + */ + public static function safeTruncate($string, $limit = 150) + { + return static::truncate($string, $limit, true); + } + + + /** + * Truncate HTML by number of characters. not "word-safe"! + * + * @param string $text + * @param int $length + * + * @return string + */ + public static function truncateHtml($text, $length = 100) + { + return Truncator::truncate($text, $length, ['length_in_chars' => true]); + } + + /** + * Truncate HTML by number of characters in a "word-safe" manor. + * + * @param string $text + * @param int $length + * + * @return string + */ + public static function safeTruncateHtml($text, $length = 100) + { + return Truncator::truncate($text, $length, ['length_in_chars' => true, 'word_safe' => true]); + } + + /** + * Generate a random string of a given length + * + * @param int $length + * + * @return string + */ + public static function generateRandomString($length = 5) + { + return substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, $length); + } + + /** + * Provides the ability to download a file to the browser + * + * @param string $file the full path to the file to be downloaded + * @param bool $force_download as opposed to letting browser choose if to download or render + */ + public static function download($file, $force_download = true) + { + if (file_exists($file)) { + // fire download event + self::getGrav()->fireEvent('onBeforeDownload', new Event(['file' => $file])); + + $file_parts = pathinfo($file); + $filesize = filesize($file); + + // check if this function is available, if so use it to stop any timeouts + try { + if (!Utils::isFunctionDisabled('set_time_limit') && !ini_get('safe_mode') && function_exists('set_time_limit')) { + set_time_limit(0); + } + } catch (\Exception $e) { + } + + ignore_user_abort(false); + + if ($force_download) { + header('Content-Description: File Transfer'); + header('Content-Type: application/octet-stream'); + header('Content-Disposition: attachment; filename=' . $file_parts['basename']); + header('Content-Transfer-Encoding: binary'); + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Pragma: public'); + } else { + header("Content-Type: " . Utils::getMimeType($file_parts['extension'])); + } + header('Content-Length: ' . $filesize); + + // 8kb chunks for now + $chunk = 8 * 1024; + + $fh = fopen($file, "rb"); + + if ($fh === false) { + return; + } + + // Repeat reading until EOF + while (!feof($fh)) { + echo fread($fh, $chunk); + + ob_flush(); // flush output + flush(); + } + + exit; + } + } + + /** + * Return the mimetype based on filename + * + * @param string $extension Extension of file (eg "txt") + * + * @return string + */ + public static function getMimeType($extension) + { + $extension = strtolower($extension); + $config = self::getGrav()['config']->get('media'); + + if (isset($config[$extension])) { + return $config[$extension]['mime']; + } + + return 'application/octet-stream'; + } + + /** + * Normalize path by processing relative `.` and `..` syntax and merging path + * + * @param string $path + * + * @return string + */ + public static function normalizePath($path) + { + $root = ($path[0] === '/') ? '/' : ''; + + $segments = explode('/', trim($path, '/')); + $ret = []; + foreach ($segments as $segment) { + if (($segment == '.') || empty($segment)) { + continue; + } + if ($segment == '..') { + array_pop($ret); + } else { + array_push($ret, $segment); + } + } + + return $root . implode('/', $ret); + } + + /** + * Check whether a function is disabled in the PHP settings + * + * @param string $function the name of the function to check + * + * @return bool + */ + public static function isFunctionDisabled($function) + { + return in_array($function, explode(',', ini_get('disable_functions'))); + } + + /** + * Get the formatted timezones list + * + * @return array + */ + public static function timezones() + { + $timezones = \DateTimeZone::listIdentifiers(\DateTimeZone::ALL); + $offsets = []; + $testDate = new \DateTime; + + foreach ($timezones as $zone) { + $tz = new \DateTimeZone($zone); + $offsets[$zone] = $tz->getOffset($testDate); + } + + asort($offsets); + + $timezone_list = []; + foreach ($offsets as $timezone => $offset) { + $offset_prefix = $offset < 0 ? '-' : '+'; + $offset_formatted = gmdate('H:i', abs($offset)); + + $pretty_offset = "UTC${offset_prefix}${offset_formatted}"; + + $timezone_list[$timezone] = "(${pretty_offset}) $timezone"; + } + + return $timezone_list; + } + + /** + * Recursively filter an array, filtering values by processing them through the $fn function argument + * + * @param array $source the Array to filter + * @param callable $fn the function to pass through each array item + * + * @return array + */ + public static function arrayFilterRecursive(Array $source, $fn) + { + $result = []; + foreach ($source as $key => $value) { + if (is_array($value)) { + $result[$key] = static::arrayFilterRecursive($value, $fn); + continue; + } + if ($fn($key, $value)) { + $result[$key] = $value; // KEEP + continue; + } + } + + return $result; + } + + /** + * Checks if the passed path contains the language code prefix + * + * @param string $string The path + * + * @return bool + */ + public static function pathPrefixedByLangCode($string) + { + if (strlen($string) <= 3) { + return false; + } + + $languages_enabled = self::getGrav()['config']->get('system.languages.supported', []); + + if ($string[0] == '/' && $string[3] == '/' && in_array(substr($string, 1, 2), $languages_enabled)) { + return true; + } + + return false; + } + + /** + * Get the timestamp of a date + * + * @param string $date a String expressed in the system.pages.dateformat.default format, with fallback to a + * strtotime argument + * + * @return int the timestamp + */ + public static function date2timestamp($date) + { + $config = self::getGrav()['config']; + $default_dateformat = $config->get('system.pages.dateformat.default'); + + // try to use DateTime and default format + if ($default_dateformat) { + $datetime = DateTime::createFromFormat($default_dateformat, $date); + } else { + $datetime = new DateTime($date); + } + + // fallback to strtotime if DateTime approach failed + if ($datetime !== false) { + return $datetime->getTimestamp(); + } else { + return strtotime($date); + } + } + + /** + * Get value of an array element using dot notation + * + * @param array $array the Array to check + * @param string $path the dot notation path to check + * @param mixed $default a value to be returned if $path is not found in $array + * + * @return mixed the value found + */ + public static function resolve(array $array, $path, $default = null) + { + $current = $array; + $p = strtok($path, '.'); + + while ($p !== false) { + if (!isset($current[$p])) { + return $default; + } + $current = $current[$p]; + $p = strtok('.'); + } + + return $current; + } + + /** + * Checks if a value is positive + * + * @param string $value + * + * @return boolean + */ + public static function isPositive($value) + { + return in_array($value, [true, 1, '1', 'yes', 'on', 'true'], true); + } + + /** + * Generates a nonce string to be hashed. Called by self::getNonce() + * We removed the IP portion in this version because it causes too many inconsistencies + * with reverse proxy setups. + * + * @param string $action + * @param bool $plusOneTick if true, generates the token for the next tick (the next 12 hours) + * + * @return string the nonce string + */ + private static function generateNonceString($action, $plusOneTick = false) + { + $username = ''; + if (isset(self::getGrav()['user'])) { + $user = self::getGrav()['user']; + $username = $user->username; + } + + $token = session_id(); + $i = self::nonceTick(); + + if ($plusOneTick) { + $i++; + } + + return ($i . '|' . $action . '|' . $username . '|' . $token . '|' . self::getGrav()['config']->get('security.salt')); + } + + //Added in version 1.0.8 to ensure that existing nonces are not broken. + private static function generateNonceStringOldStyle($action, $plusOneTick = false) + { + if (isset(self::getGrav()['user'])) { + $user = self::getGrav()['user']; + $username = $user->username; + if (isset($_SERVER['REMOTE_ADDR'])) { + $username .= $_SERVER['REMOTE_ADDR']; + } + } else { + $username = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : ''; + } + $token = session_id(); + $i = self::nonceTick(); + if ($plusOneTick) { + $i++; + } + + return ($i . '|' . $action . '|' . $username . '|' . $token . '|' . self::getGrav()['config']->get('security.salt')); + } + + /** + * Get the time-dependent variable for nonce creation. + * + * Now a tick lasts a day. Once the day is passed, the nonce is not valid any more. Find a better way + * to ensure nonces issued near the end of the day do not expire in that small amount of time + * + * @return int the time part of the nonce. Changes once every 24 hours + */ + private static function nonceTick() + { + $secondsInHalfADay = 60 * 60 * 12; + + return (int)ceil(time() / ($secondsInHalfADay)); + } + + /** + * Creates a hashed nonce tied to the passed action. Tied to the current user and time. The nonce for a given + * action is the same for 12 hours. + * + * @param string $action the action the nonce is tied to (e.g. save-user-admin or move-page-homepage) + * @param bool $plusOneTick if true, generates the token for the next tick (the next 12 hours) + * + * @return string the nonce + */ + public static function getNonce($action, $plusOneTick = false) + { + // Don't regenerate this again if not needed + if (isset(static::$nonces[$action])) { + return static::$nonces[$action]; + } + $nonce = md5(self::generateNonceString($action, $plusOneTick)); + static::$nonces[$action] = $nonce; + + return static::$nonces[$action]; + } + + //Added in version 1.0.8 to ensure that existing nonces are not broken. + public static function getNonceOldStyle($action, $plusOneTick = false) + { + // Don't regenerate this again if not needed + if (isset(static::$nonces[$action])) { + return static::$nonces[$action]; + } + $nonce = md5(self::generateNonceStringOldStyle($action, $plusOneTick)); + static::$nonces[$action] = $nonce; + + return static::$nonces[$action]; + } + + /** + * Verify the passed nonce for the give action + * + * @param string $nonce the nonce to verify + * @param string $action the action to verify the nonce to + * + * @return boolean verified or not + */ + public static function verifyNonce($nonce, $action) + { + //Safety check for multiple nonces + if (is_array($nonce)) { + $nonce = array_shift($nonce); + } + + //Nonce generated 0-12 hours ago + if ($nonce == self::getNonce($action)) { + return true; + } + + //Nonce generated 12-24 hours ago + $plusOneTick = true; + if ($nonce == self::getNonce($action, $plusOneTick)) { + return true; + } + + + //Added in version 1.0.8 to ensure that existing nonces are not broken. + //Nonce generated 0-12 hours ago + if ($nonce == self::getNonceOldStyle($action)) { + return true; + } + + //Nonce generated 12-24 hours ago + $plusOneTick = true; + if ($nonce == self::getNonceOldStyle($action, $plusOneTick)) { + return true; + } + + //Invalid nonce + return false; + } +} diff --git a/system/src/Grav/Console/Cli/BackupCommand.php b/system/src/Grav/Console/Cli/BackupCommand.php new file mode 100644 index 0000000..d8b8500 --- /dev/null +++ b/system/src/Grav/Console/Cli/BackupCommand.php @@ -0,0 +1,83 @@ +setName("backup") + ->addArgument( + 'destination', + InputArgument::OPTIONAL, + 'Where to store the backup (/backup is default)' + + ) + ->setDescription("Creates a backup of the Grav instance") + ->setHelp('The backup creates a zipped backup. Optionally can be saved in a different destination.'); + + $this->source = getcwd(); + } + + /** + * @return int|null|void + */ + protected function serve() + { + $this->progress = new ProgressBar($this->output); + $this->progress->setFormat('Archiving %current% files [%bar%] %elapsed:6s% %memory:6s%'); + + self::getGrav()['config']->init(); + + $destination = ($this->input->getArgument('destination')) ? $this->input->getArgument('destination') : null; + $log = JsonFile::instance(self::getGrav()['locator']->findResource("log://backup.log", true, true)); + $backup = ZipBackup::backup($destination, [$this, 'output']); + + $log->content([ + 'time' => time(), + 'location' => $backup + ]); + $log->save(); + + $this->output->writeln(''); + $this->output->writeln(''); + + } + + /** + * @param $args + */ + public function output($args) + { + switch ($args['type']) { + case 'message': + $this->output->writeln($args['message']); + break; + case 'progress': + if ($args['complete']) { + $this->progress->finish(); + } else { + $this->progress->advance(); + } + break; + } + } + +} + diff --git a/system/src/Grav/Console/Cli/CleanCommand.php b/system/src/Grav/Console/Cli/CleanCommand.php new file mode 100644 index 0000000..ff6d286 --- /dev/null +++ b/system/src/Grav/Console/Cli/CleanCommand.php @@ -0,0 +1,237 @@ +setName("clean") + ->setDescription("Handles cleaning chores for Grav distribution") + ->setHelp('The clean clean extraneous folders and data'); + } + + /** + * @return int|null|void + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->setupConsole($input, $output); + + $this->cleanPaths(); + } + + private function cleanPaths() + { + $this->output->writeln(''); + $this->output->writeln('DELETING'); + $anything = false; + foreach ($this->paths_to_remove as $path) { + $path = ROOT_DIR . $path; + if (is_dir($path) && @Folder::delete($path)) { + $anything = true; + $this->output->writeln('dir: ' . $path); + } elseif (is_file($path) && @unlink($path)) { + $anything = true; + $this->output->writeln('file: ' . $path); + } + } + if (!$anything) { + $this->output->writeln(''); + $this->output->writeln('Nothing to clean...'); + } + } + + /** + * Set colors style definition for the formatter. + * + * @param InputInterface $input + * @param OutputInterface $output + */ + public function setupConsole(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->output = $output; + + $this->output->getFormatter()->setStyle('normal', new OutputFormatterStyle('white')); + $this->output->getFormatter()->setStyle('yellow', new OutputFormatterStyle('yellow', null, array('bold'))); + $this->output->getFormatter()->setStyle('red', new OutputFormatterStyle('red', null, array('bold'))); + $this->output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan', null, array('bold'))); + $this->output->getFormatter()->setStyle('green', new OutputFormatterStyle('green', null, array('bold'))); + $this->output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta', null, array('bold'))); + $this->output->getFormatter()->setStyle('white', new OutputFormatterStyle('white', null, array('bold'))); + } + +} diff --git a/system/src/Grav/Console/Cli/ClearCacheCommand.php b/system/src/Grav/Console/Cli/ClearCacheCommand.php new file mode 100644 index 0000000..a675a66 --- /dev/null +++ b/system/src/Grav/Console/Cli/ClearCacheCommand.php @@ -0,0 +1,64 @@ +setName('clear-cache') + ->setAliases(['clearcache']) + ->setDescription('Clears Grav cache') + ->addOption('all', null, InputOption::VALUE_NONE, 'If set will remove all including compiled, twig, doctrine caches') + ->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/*') + ->setHelp('The clear-cache deletes all cache files'); + } + + /** + * @return int|null|void + */ + protected function serve() + { + $this->cleanPaths(); + } + + /** + * loops over the array of paths and deletes the files/folders + */ + private function cleanPaths() + { + $this->output->writeln(''); + $this->output->writeln('Clearing cache'); + $this->output->writeln(''); + + if ($this->input->getOption('all')) { + $remove = 'all'; + } elseif ($this->input->getOption('assets-only')) { + $remove = 'assets-only'; + } elseif ($this->input->getOption('images-only')) { + $remove = 'images-only'; + } elseif ($this->input->getOption('cache-only')) { + $remove = 'cache-only'; + } else { + $remove = 'standard'; + } + + foreach (Cache::clearCache($remove) as $result) { + $this->output->writeln($result); + } + } +} + diff --git a/system/src/Grav/Console/Cli/ComposerCommand.php b/system/src/Grav/Console/Cli/ComposerCommand.php new file mode 100644 index 0000000..729b425 --- /dev/null +++ b/system/src/Grav/Console/Cli/ComposerCommand.php @@ -0,0 +1,70 @@ +setName("composer") + ->addOption( + 'install', + 'i', + InputOption::VALUE_NONE, + 'install the dependencies' + ) + ->addOption( + 'update', + 'u', + InputOption::VALUE_NONE, + 'update the dependencies' + ) + ->setDescription("Updates the composer vendor dependencies needed by Grav.") + ->setHelp('The composer command updates the composer vendor dependencies needed by Grav'); + } + + /** + * @return int|null|void + */ + protected function serve() + { + $action = $this->input->getOption('install') ? 'install' : ($this->input->getOption('update') ? 'update' : 'install'); + + if ($this->input->getOption('install')) { + $action = 'install'; + } + + // Updates composer first + $this->output->writeln("\nInstalling vendor dependencies"); + $this->output->writeln($this->composerUpdate(GRAV_ROOT, $action)); + } + +} diff --git a/system/src/Grav/Console/Cli/InstallCommand.php b/system/src/Grav/Console/Cli/InstallCommand.php new file mode 100644 index 0000000..31f0116 --- /dev/null +++ b/system/src/Grav/Console/Cli/InstallCommand.php @@ -0,0 +1,162 @@ +setName("install") + ->addOption( + 'symlink', + 's', + InputOption::VALUE_NONE, + 'Symlink the required bits' + ) + ->addArgument( + 'destination', + InputArgument::OPTIONAL, + 'Where to install the required bits (default to current project)' + ) + ->setDescription("Installs the dependencies needed by Grav. Optionally can create symbolic links") + ->setHelp('The install command installs the dependencies needed by Grav. Optionally can create symbolic links'); + } + + /** + * @return int|null|void + */ + protected function serve() + { + $dependencies_file = '.dependencies'; + $this->destination = ($this->input->getArgument('destination')) ? $this->input->getArgument('destination') : ROOT_DIR; + + // fix trailing slash + $this->destination = rtrim($this->destination, DS) . DS; + $this->user_path = $this->destination . USER_PATH; + + if (false === $this->isWindows()) { + $local_config_file = exec('eval echo ~/.grav/config'); + if (file_exists($local_config_file)) { + $this->local_config = Yaml::parse($local_config_file); + $this->output->writeln('Read local config from ' . $local_config_file . ''); + } + } + + // Look for dependencies file in ROOT and USER dir + if (file_exists($this->user_path . $dependencies_file)) { + $this->config = Yaml::parse($this->user_path . $dependencies_file); + } elseif (file_exists($this->destination . $dependencies_file)) { + $this->config = Yaml::parse($this->destination . $dependencies_file); + } else { + $this->output->writeln('ERROR Missing .dependencies file in user/ folder'); + } + + // If yaml config, process + if ($this->config) { + if (!$this->input->getOption('symlink')) { + // Updates composer first + $this->output->writeln("\nInstalling vendor dependencies"); + $this->output->writeln($this->composerUpdate(GRAV_ROOT, 'install')); + + $this->gitclone(); + } else { + $this->symlink(); + } + } else { + $this->output->writeln('ERROR invalid YAML in ' . $dependencies_file); + } + + + } + + /** + * Clones from Git + */ + private function gitclone() + { + $this->output->writeln(''); + $this->output->writeln('Cloning Bits'); + $this->output->writeln('============'); + $this->output->writeln(''); + + foreach ($this->config['git'] as $repo => $data) { + $path = $this->destination . DS . $data['path']; + if (!file_exists($path)) { + exec('cd "' . $this->destination . '" && git clone -b ' . $data['branch'] . ' ' . $data['url'] . ' ' . $data['path']); + $this->output->writeln('SUCCESS cloned ' . $data['url'] . ' -> ' . $path . ''); + $this->output->writeln(''); + } else { + $this->output->writeln('' . $path . ' already exists, skipping...'); + $this->output->writeln(''); + } + + } + } + + /** + * Symlinks + */ + private function symlink() + { + $this->output->writeln(''); + $this->output->writeln('Symlinking Bits'); + $this->output->writeln('==============='); + $this->output->writeln(''); + + if (!$this->local_config) { + $this->output->writeln('No local configuration available, aborting...'); + $this->output->writeln(''); + return; + } + + exec('cd ' . $this->destination); + foreach ($this->config['links'] as $repo => $data) { + $from = $this->local_config[$data['scm'] . '_repos'] . $data['src']; + $to = $this->destination . $data['path']; + + if (file_exists($from)) { + if (!file_exists($to)) { + symlink($from, $to); + $this->output->writeln('SUCCESS symlinked ' . $data['src'] . ' -> ' . $data['path'] . ''); + $this->output->writeln(''); + } else { + $this->output->writeln('destination: ' . $to . ' already exists, skipping...'); + $this->output->writeln(''); + } + } else { + $this->output->writeln('source: ' . $from . ' does not exists, skipping...'); + $this->output->writeln(''); + } + + } + } +} diff --git a/system/src/Grav/Console/Cli/NewProjectCommand.php b/system/src/Grav/Console/Cli/NewProjectCommand.php new file mode 100644 index 0000000..2d63f04 --- /dev/null +++ b/system/src/Grav/Console/Cli/NewProjectCommand.php @@ -0,0 +1,62 @@ +setName('new-project') + ->setAliases(['newproject']) + ->addArgument( + 'destination', + InputArgument::REQUIRED, + 'The destination directory of your new Grav project' + ) + ->addOption( + 'symlink', + 's', + InputOption::VALUE_NONE, + 'Symlink the required bits' + ) + ->setDescription('Creates a new Grav project with all the dependencies installed') + ->setHelp('The new-project command is a combination of the `setup` and `install` commands.\nCreates a new Grav instance and performs the installation of all the required dependencies.'); + } + + /** + * @return int|null|void + */ + protected function serve() + { + $sandboxCommand = $this->getApplication()->find('sandbox'); + $installCommand = $this->getApplication()->find('install'); + + $sandboxArguments = new ArrayInput(array( + 'command' => 'sandbox', + 'destination' => $this->input->getArgument('destination'), + '-s' => $this->input->getOption('symlink') + )); + + $installArguments = new ArrayInput(array( + 'command' => 'install', + 'destination' => $this->input->getArgument('destination'), + '-s' => $this->input->getOption('symlink') + )); + + $sandboxCommand->run($sandboxArguments, $this->output); + $installCommand->run($installArguments, $this->output); + + } +} diff --git a/system/src/Grav/Console/Cli/NewUserCommand.php b/system/src/Grav/Console/Cli/NewUserCommand.php new file mode 100644 index 0000000..694d5ee --- /dev/null +++ b/system/src/Grav/Console/Cli/NewUserCommand.php @@ -0,0 +1,34 @@ +setName('newuser') + ->setDescription('DEPRECATED: Creates a new user') + ->setHelp('The newuser from `bin/grav` has been deprecated. Please refer to `bin/plugin login new-user') + ; + } + + /** + * @return int|null|void + */ + protected function serve() + { + $this->output->writeln(''); + $this->output->writeln('DEPRECATED COMMAND'); + $this->output->writeln(' `bin/grav new-user` has been deprecated in favor of the new `bin/plugin login new-user`'); + } +} diff --git a/system/src/Grav/Console/Cli/SandboxCommand.php b/system/src/Grav/Console/Cli/SandboxCommand.php new file mode 100644 index 0000000..be692dc --- /dev/null +++ b/system/src/Grav/Console/Cli/SandboxCommand.php @@ -0,0 +1,304 @@ + '/.editorconfig', + '/.gitignore' => '/.gitignore', + '/CHANGELOG.md' => '/CHANGELOG.md', + '/LICENSE.txt' => '/LICENSE.txt', + '/README.md' => '/README.md', + '/CONTRIBUTING.md' => '/CONTRIBUTING.md', + '/index.php' => '/index.php', + '/composer.json' => '/composer.json', + '/bin' => '/bin', + '/system' => '/system', + '/vendor' => '/vendor', + '/webserver-configs' => '/webserver-configs', + '/codeception.yml' => '/codeception.yml', + ); + + /** + * @var string + */ + + protected $default_file = "---\ntitle: HomePage\n---\n# HomePage\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque porttitor eu felis sed ornare. Sed a mauris venenatis, pulvinar velit vel, dictum enim. Phasellus ac rutrum velit. Nunc lorem purus, hendrerit sit amet augue aliquet, iaculis ultricies nisl. Suspendisse tincidunt euismod risus, quis feugiat arcu tincidunt eget. Nulla eros mi, commodo vel ipsum vel, aliquet congue odio. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque velit orci, laoreet at adipiscing eu, interdum quis nibh. Nunc a accumsan purus."; + + protected $source; + protected $destination; + + /** + * + */ + protected function configure() + { + $this + ->setName('sandbox') + ->setDescription('Setup of a base Grav system in your webroot, good for development, playing around or starting fresh') + ->addArgument( + 'destination', + InputArgument::REQUIRED, + 'The destination directory to symlink into' + ) + ->addOption( + 'symlink', + 's', + InputOption::VALUE_NONE, + 'Symlink the base grav system' + ) + ->setHelp("The sandbox command help create a development environment that can optionally use symbolic links to link the core of grav to the git cloned repository.\nGood for development, playing around or starting fresh"); + $this->source = getcwd(); + } + + /** + * @return int|null|void + */ + protected function serve() + { + $this->destination = $this->input->getArgument('destination'); + + // Symlink the Core Stuff + if ($this->input->getOption('symlink')) { + // Create Some core stuff if it doesn't exist + $this->createDirectories(); + + // Loop through the symlink mappings and create the symlinks + $this->symlink(); + + // Copy the Core STuff + } else { + // Create Some core stuff if it doesn't exist + $this->createDirectories(); + + // Loop through the symlink mappings and copy what otherwise would be symlinks + $this->copy(); + } + + $this->pages(); + $this->initFiles(); + $this->perms(); + } + + /** + * + */ + private function createDirectories() + { + $this->output->writeln(''); + $this->output->writeln('Creating Directories'); + $dirs_created = false; + + if (!file_exists($this->destination)) { + mkdir($this->destination, 0777, true); + } + + foreach ($this->directories as $dir) { + if (!file_exists($this->destination . $dir)) { + $dirs_created = true; + $this->output->writeln(' ' . $dir . ''); + mkdir($this->destination . $dir, 0777, true); + } + } + + if (!$dirs_created) { + $this->output->writeln(' Directories already exist'); + } + } + + /** + * + */ + private function copy() + { + $this->output->writeln(''); + $this->output->writeln('Copying Files'); + + + foreach ($this->mappings as $source => $target) { + if ((int)$source == $source) { + $source = $target; + } + + $from = $this->source . $source; + $to = $this->destination . $target; + + $this->output->writeln(' ' . $source . ' -> ' . $to); + @Folder::rcopy($from, $to); + } + } + + /** + * + */ + private function symlink() + { + $this->output->writeln(''); + $this->output->writeln('Resetting Symbolic Links'); + + + foreach ($this->mappings as $source => $target) { + if ((int)$source == $source) { + $source = $target; + } + + $from = $this->source . $source; + $to = $this->destination . $target; + + $this->output->writeln(' ' . $source . ' -> ' . $to); + + if (is_dir($to)) { + @Folder::delete($to); + } else { + @unlink($to); + } + symlink($from, $to); + } + } + + /** + * + */ + private function initFiles() + { + $this->check($this->output); + + $this->output->writeln(''); + $this->output->writeln('File Initializing'); + $files_init = false; + + // Copy files if they do not exist + foreach ($this->files as $source => $target) { + if ((int)$source == $source) { + $source = $target; + } + + $from = $this->source . $source; + $to = $this->destination . $target; + + if (!file_exists($to)) { + $files_init = true; + copy($from, $to); + $this->output->writeln(' ' . $target . ' -> Created'); + } + } + + if (!$files_init) { + $this->output->writeln(' Files already exist'); + } + + + } + + /** + * + */ + private function pages() + { + $this->output->writeln(''); + $this->output->writeln('Pages Initializing'); + + // get pages files and initialize if no pages exist + $pages_dir = $this->destination . '/user/pages'; + $pages_files = array_diff(scandir($pages_dir), array('..', '.')); + + if (count($pages_files) == 0) { + $destination = $this->source . '/user/pages'; + Folder::rcopy($destination, $pages_dir); + $this->output->writeln(' ' . $destination . ' -> Created'); + + } + } + + /** + * + */ + private function perms() + { + $this->output->writeln(''); + $this->output->writeln('Permissions Initializing'); + + $dir_perms = 0755; + + $binaries = glob($this->destination . DS . 'bin' . DS . '*'); + + foreach ($binaries as $bin) { + chmod($bin, $dir_perms); + $this->output->writeln(' bin/' . basename($bin) . ' permissions reset to ' . decoct($dir_perms)); + } + + $this->output->writeln(""); + } + + + /** + * + */ + private function check() + { + $success = true; + + if (!file_exists($this->destination)) { + $this->output->writeln(' file: $this->destination does not exist!'); + $success = false; + } + + foreach ($this->directories as $dir) { + if (!file_exists($this->destination . $dir)) { + $this->output->writeln(' directory: ' . $dir . ' does not exist!'); + $success = false; + } + } + + foreach ($this->mappings as $target => $link) { + if (!file_exists($this->destination . $target)) { + $this->output->writeln(' mappings: ' . $target . ' does not exist!'); + $success = false; + } + } + if (!$success) { + $this->output->writeln(''); + $this->output->writeln('install should be run with --symlink|--s to symlink first'); + exit; + } + } +} diff --git a/system/src/Grav/Console/ConsoleCommand.php b/system/src/Grav/Console/ConsoleCommand.php new file mode 100644 index 0000000..349c77a --- /dev/null +++ b/system/src/Grav/Console/ConsoleCommand.php @@ -0,0 +1,38 @@ +setupConsole($input, $output); + $this->serve(); + } + + /** + * + */ + protected function serve() + { + + } + +} diff --git a/system/src/Grav/Console/ConsoleTrait.php b/system/src/Grav/Console/ConsoleTrait.php new file mode 100644 index 0000000..1ba9357 --- /dev/null +++ b/system/src/Grav/Console/ConsoleTrait.php @@ -0,0 +1,127 @@ +set('system.cache.driver', 'default'); + } + + $this->argv = $_SERVER['argv'][0]; + + $this->input = $input; + $this->output = $output; + + $this->output->getFormatter()->setStyle('normal', new OutputFormatterStyle('white')); + $this->output->getFormatter()->setStyle('yellow', new OutputFormatterStyle('yellow', null, array('bold'))); + $this->output->getFormatter()->setStyle('red', new OutputFormatterStyle('red', null, array('bold'))); + $this->output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan', null, array('bold'))); + $this->output->getFormatter()->setStyle('green', new OutputFormatterStyle('green', null, array('bold'))); + $this->output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta', null, array('bold'))); + $this->output->getFormatter()->setStyle('white', new OutputFormatterStyle('white', null, array('bold'))); + } + + /** + * @param $path + */ + public function isGravInstance($path) + { + if (!file_exists($path)) { + $this->output->writeln(''); + $this->output->writeln("ERROR: Destination doesn't exist:"); + $this->output->writeln(" $path"); + $this->output->writeln(''); + exit; + } + + if (!is_dir($path)) { + $this->output->writeln(''); + $this->output->writeln("ERROR: Destination chosen to install is not a directory:"); + $this->output->writeln(" $path"); + $this->output->writeln(''); + exit; + } + + if (!file_exists($path . DS . 'index.php') || !file_exists($path . DS . '.dependencies') || !file_exists($path . DS . 'system' . DS . 'config' . DS . 'system.yaml')) { + $this->output->writeln(''); + $this->output->writeln("ERROR: Destination chosen to install does not appear to be a Grav instance:"); + $this->output->writeln(" $path"); + $this->output->writeln(''); + exit; + } + } + + public function composerUpdate($path, $action = 'install') + { + $composer = Composer::getComposerExecutor(); + + return system($composer . ' --working-dir="'.$path.'" --no-interaction --no-dev --prefer-dist -o '. $action); + } + + /** + * @param array $all + * + * @return int + * @throws \Exception + */ + public function clearCache($all = []) + { + if ($all) { + $all = ['--all' => true]; + } + + $command = new ClearCacheCommand(); + $input = new ArrayInput($all); + return $command->run($input, $this->output); + } + + /** + * Validate if the system is based on windows or not. + * + * @return bool + */ + public function isWindows() + { + $keys = [ + 'CYGWIN_NT-5.1', + 'WIN32', + 'WINNT', + 'Windows' + ]; + + return array_key_exists(PHP_OS, $keys); + } +} diff --git a/system/src/Grav/Console/Gpm/IndexCommand.php b/system/src/Grav/Console/Gpm/IndexCommand.php new file mode 100644 index 0000000..942c0f4 --- /dev/null +++ b/system/src/Grav/Console/Gpm/IndexCommand.php @@ -0,0 +1,243 @@ +setName("index") + ->addOption( + 'force', + 'f', + 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( + 'installed-only', + 'I', + InputOption::VALUE_NONE, + 'Filters the results to only the Themes and Plugins you have installed' + ) + ->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') + ; + } + + /** + * @return int|null|void + */ + protected function serve() + { + $this->options = $this->input->getOptions(); + + $this->gpm = new GPM($this->options['force']); + + $this->data = $this->gpm->getRepository(); + + $this->output->writeln(''); + + $data = $this->filter($this->data); + + foreach ($data as $type => $packages) { + $this->output->writeln("" . ucfirst($type) . " [ " . count($packages) . " ]"); + + $index = 0; + $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) + ); + } + + $this->output->writeln(''); + } + + $this->output->writeln('You can either get more informations about a package by typing:'); + $this->output->writeln(' ' . $this->argv . ' info '); + $this->output->writeln(''); + $this->output->writeln('Or you can install a package by typing:'); + $this->output->writeln(' ' . $this->argv . ' install '); + $this->output->writeln(''); + } + + /** + * @param $package + * + * @return string + */ + private function versionDetails($package) + { + $list = $this->gpm->{'getUpdatable' . ucfirst($package->package_type)}(); + $package = isset($list[$package->slug]) ? $list[$package->slug] : $package; + $type = ucfirst(preg_replace("/s$/", '', $package->package_type)); + $updatable = $this->gpm->{'is' . $type . 'Updatable'}($package->slug); + $installed = $this->gpm->{'is' . $type . 'Installed'}($package->slug); + $local = $this->gpm->{'getInstalled' . $type}($package->slug); + + if (!$installed || !$updatable) { + $version = $installed ? $local->version : $package->version; + $installed = !$installed ? ' (not installed)' : ' (installed)'; + + return str_pad(" [v" . $version . "]", 35) . $installed; + } + + if ($updatable) { + $installed = !$installed ? ' (not installed)' : ' (installed)'; + + return str_pad(" [v" . $package->version . " ➜ v" . $package->available . "]", + 61) . $installed; + } + + 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']); + } + + $filter = [ + $this->options['filter'], + $this->options['installed-only'], + $this->options['updates-only'], + $this->options['desc'] + ]; + + if (count(array_filter($filter))) { + 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['installed-only'] && $filter) { + $method = ucfirst(preg_replace("/s$/", '', $package->package_type)); + $filter = $this->gpm->{'is' . $method . 'Installed'}($package->slug); + } + + // 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; + } +} diff --git a/system/src/Grav/Console/Gpm/InfoCommand.php b/system/src/Grav/Console/Gpm/InfoCommand.php new file mode 100644 index 0000000..99f8bd2 --- /dev/null +++ b/system/src/Grav/Console/Gpm/InfoCommand.php @@ -0,0 +1,176 @@ +setName("info") + ->addOption( + 'force', + 'f', + InputOption::VALUE_NONE, + 'Force fetching the new data remotely' + ) + ->addOption( + 'all-yes', + 'y', + InputOption::VALUE_NONE, + 'Assumes yes (or best approach) instead of prompting' + ) + ->addArgument( + 'package', + InputArgument::REQUIRED, + 'The package of which more informations are desired. Use the "index" command for a list of packages' + ) + ->setDescription("Shows more informations about a package") + ->setHelp('The info shows more informations about a package'); + } + + /** + * @return int|null|void + */ + protected function serve() + { + $this->gpm = new GPM($this->input->getOption('force')); + + $foundPackage = $this->gpm->findPackage($this->input->getArgument('package')); + + if (!$foundPackage) { + $this->output->writeln("The package '" . $this->input->getArgument('package') . "' was not found in the Grav repository."); + $this->output->writeln(''); + $this->output->writeln("You can list all the available packages by typing:"); + $this->output->writeln(" " . $this->argv . " index"); + $this->output->writeln(''); + exit; + } + + $this->output->writeln("Found package '" . $this->input->getArgument('package') . "' under the '" . ucfirst($foundPackage->package_type) . "' section"); + $this->output->writeln(''); + $this->output->writeln("" . $foundPackage->name . " [" . $foundPackage->slug . "]"); + $this->output->writeln(str_repeat('-', strlen($foundPackage->name) + strlen($foundPackage->slug) + 3)); + $this->output->writeln("" . strip_tags($foundPackage->description_plain) . ""); + $this->output->writeln(''); + + $packageURL = ''; + if (isset($foundPackage->author['url'])) { + $packageURL = '<' . $foundPackage->author['url'] . '>'; + } + + $this->output->writeln("" . str_pad("Author", + 12) . ": " . $foundPackage->author['name'] . ' <' . $foundPackage->author['email'] . '> ' . $packageURL); + + foreach (array( + 'version', + 'keywords', + 'date', + 'homepage', + 'demo', + 'docs', + 'guide', + 'repository', + 'bugs', + 'zipball_url', + 'license' + ) as $info) { + if (isset($foundPackage->$info)) { + $name = ucfirst($info); + $data = $foundPackage->$info; + + if ($info == 'zipball_url') { + $name = "Download"; + } + + if ($info == 'date') { + $name = "Last Update"; + $data = date('D, j M Y, H:i:s, P ', strtotime('2014-09-16T00:07:16Z')); + } + + $name = str_pad($name, 12); + $this->output->writeln("" . $name . ": " . $data); + } + } + + $type = rtrim($foundPackage->package_type, 's'); + $updatable = $this->gpm->{'is' . $type . 'Updatable'}($foundPackage->slug); + $installed = $this->gpm->{'is' . $type . 'Installed'}($foundPackage->slug); + + // display current version if installed and different + if ($installed && $updatable) { + $local = $this->gpm->{'getInstalled'. $type}($foundPackage->slug); + $this->output->writeln(''); + $this->output->writeln("Currently installed version: " . $local->version . ""); + $this->output->writeln(''); + } + + // 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; + + $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(""); + } + } + } + + + $this->output->writeln(''); + + if ($installed && $updatable) { + $this->output->writeln("You can update this package by typing:"); + $this->output->writeln(" " . $this->argv . " update " . $foundPackage->slug . ""); + } else { + $this->output->writeln("You can install this package by typing:"); + $this->output->writeln(" " . $this->argv . " install " . $foundPackage->slug . ""); + } + + $this->output->writeln(''); + + } +} diff --git a/system/src/Grav/Console/Gpm/InstallCommand.php b/system/src/Grav/Console/Gpm/InstallCommand.php new file mode 100644 index 0000000..dca4fe7 --- /dev/null +++ b/system/src/Grav/Console/Gpm/InstallCommand.php @@ -0,0 +1,522 @@ +setName("install") + ->addOption( + 'force', + 'f', + InputOption::VALUE_NONE, + 'Force re-fetching the data from remote' + ) + ->addOption( + 'all-yes', + 'y', + InputOption::VALUE_NONE, + 'Assumes yes (or best approach) instead of prompting' + ) + ->addOption( + 'destination', + 'd', + InputOption::VALUE_OPTIONAL, + 'The destination where the package should be installed at. By default this would be where the grav instance has been launched from', + GRAV_ROOT + ) + ->addArgument( + 'package', + InputArgument::IS_ARRAY | InputArgument::REQUIRED, + 'The package(s) that are desired to be installed. Use the "index" command for a list of packages' + ) + ->setDescription("Performs the installation of plugins and themes") + ->setHelp('The install command allows to install plugins and themes'); + } + + /** + * @return int|null|void + */ + protected function serve() + { + $this->gpm = new GPM($this->input->getOption('force')); + $this->destination = realpath($this->input->getOption('destination')); + + $packages = array_map('strtolower', $this->input->getArgument('package')); + $this->data = $this->gpm->findPackages($packages); + + if (false === $this->isWindows() && @is_file(getenv("HOME").'/.grav/config')) { + $local_config_file = exec('eval echo ~/.grav/config'); + if (file_exists($local_config_file)) { + $this->local_config = Yaml::parse($local_config_file); + } + } + + if ( + !Installer::isGravInstance($this->destination) || + !Installer::isValidDestination($this->destination, [Installer::EXISTS, Installer::IS_LINK]) + ) { + $this->output->writeln("ERROR: " . Installer::lastErrorMsg()); + exit; + } + + $this->output->writeln(''); + + if (!$this->data['total']) { + $this->output->writeln("Nothing to install."); + $this->output->writeln(''); + exit; + } + + if (count($this->data['not_found'])) { + $this->output->writeln("These packages were not found on Grav: " . implode(', ', + array_keys($this->data['not_found'])) . ""); + } + + unset($this->data['not_found']); + unset($this->data['total']); + + foreach ($this->data as $data) { + foreach ($data as $package) { + //Check for dependencies + if (isset($package->dependencies)) { + $this->output->writeln("Package " . $package->name . " has ". count($package->dependencies) . " required dependencies that must be installed first..."); + $this->output->writeln(''); + + $dependency_data = $this->gpm->findPackages($package->dependencies); + + if (!$dependency_data['total']) { + $this->output->writeln("No dependencies found..."); + $this->output->writeln(''); + } else { + unset($dependency_data['total']); + + foreach($dependency_data as $type => $dep_data) { + foreach($dep_data as $name => $dep_package) { + + $this->processPackage($dep_package); + } + } + } + } + + $this->processPackage($package); + } + } + + // clear cache after successful upgrade + $this->clearCache(); + } + + /** + * @param $package + */ + private function processPackage($package) + { + $install_options = ['GPM']; + + // if no name, not found in GPM + if (!isset($package->version)) { + unset($install_options[0]); + } + // if local config found symlink is a valid option + if (isset($this->local_config) && $this->getSymlinkSource($package)) { + $install_options[] = 'Symlink'; + } + // if override set, can install via git + if (isset($package->override_repository)) { + $install_options[] = 'Git'; + } + + // reindex list + $install_options = array_values($install_options); + + if (count($install_options) == 0) { + // no valid install options - error and return + $this->output->writeln("not valid installation methods found!"); + return; + } elseif (count($install_options) == 1) { + // only one option, use it... + $method = $install_options[0]; + } else { + $helper = $this->getHelper('question'); + $question = new ChoiceQuestion( + 'Please select installation method for ' . $package->name . ' ('.$install_options[0].' is default)', array_values($install_options), 0 + ); + $question->setErrorMessage('Method %s is invalid'); + $method = $helper->ask($this->input, $this->output, $question); + } + + $this->output->writeln(''); + + $method_name = 'process'.$method; + $this->$method_name($package); + + $this->installDemoContent($package); + } + + + /** + * @param $package + */ + private function installDemoContent($package) + { + $demo_dir = $this->destination . DS . $package->install_path . DS . '_demo'; + $dest_dir = $this->destination . DS . 'user'; + $pages_dir = $dest_dir . DS . 'pages'; + + if (file_exists($demo_dir)) { + // Demo content exists, prompt to install it. + $this->output->writeln("Attention: ".$package->name . " contains demo content"); + $helper = $this->getHelper('question'); + $question = new ConfirmationQuestion('Do you wish to install this demo content? [y|N] ', false); + + if (!$helper->ask($this->input, $this->output, $question)) { + $this->output->writeln(" '- Skipped! "); + $this->output->writeln(''); + return; + } + + // if pages folder exists in demo + if (file_exists($demo_dir . DS . 'pages')) { + $pages_backup = 'pages.' . date('m-d-Y-H-i-s'); + $question = new ConfirmationQuestion('This will backup your current `user/pages` folder to `user/'. $pages_backup. '`, continue? [y|N]', false); + + if (!$helper->ask($this->input, $this->output, $question)) { + $this->output->writeln(" '- Skipped! "); + $this->output->writeln(''); + return; + } + + // backup current pages folder + if (file_exists($dest_dir)) { + if (rename($pages_dir, $dest_dir . DS . $pages_backup)) { + $this->output->writeln(" |- Backing up pages... ok"); + } else { + $this->output->writeln(" |- Backing up pages... failed"); + } + } + } + + // Confirmation received, copy over the data + $this->output->writeln(" |- Installing demo content... ok "); + Folder::rcopy($demo_dir, $dest_dir); + $this->output->writeln(" '- Success! "); + $this->output->writeln(''); + } + } + + /** + * @param $package + * + * @return array + */ + private function getGitRegexMatches($package) + { + if (isset($package->override_repository)) { + $repository = $package->override_repository; + } elseif (isset($package->repository)) { + $repository = $package->repository; + } else { + return false; + } + + preg_match(GIT_REGEX, $repository, $matches); + + return $matches; + } + + /** + * @param $package + * + * @return bool|string + */ + private function getSymlinkSource($package) + { + $matches = $this->getGitRegexMatches($package); + + foreach ($this->local_config as $path) { + if (Utils::endsWith($matches[2], '.git')) { + $repo_dir = preg_replace('/\.git$/', '', $matches[2]); + } else { + $repo_dir = $matches[2]; + } + + $from = rtrim($path, '/') . '/' . $repo_dir; + + if (file_exists($from)) { + return $from; + } + } + return false; + } + + /** + * @param $package + */ + private function processSymlink($package) + { + + exec('cd ' . $this->destination); + + $to = $this->destination . DS . $package->install_path; + $from = $this->getSymlinkSource($package); + + $this->output->writeln("Preparing to Symlink " . $package->name . ""); + $this->output->write(" |- Checking source... "); + + if (file_exists($from)) { + $this->output->writeln("ok"); + + $this->output->write(" |- Checking destination... "); + $checks = $this->checkDestination($package); + + if (!$checks) { + $this->output->writeln(" '- Installation failed or aborted."); + $this->output->writeln(''); + } else { + if (file_exists($to)) { + $this->output->writeln(" '- Symlink cannot overwrite an existing package, please remove first"); + $this->output->writeln(''); + } else { + symlink($from, $to); + + // extra white spaces to clear out the buffer properly + $this->output->writeln(" |- Symlinking package... ok "); + + $this->output->writeln(" '- Success! "); + $this->output->writeln(''); + } + + + } + return; + } + + $this->output->writeln("not found!"); + $this->output->writeln(" '- Installation failed or aborted."); + } + + /** + * @param $package + */ + private function processGit($package) + { + $matches = $this->getGitRegexMatches($package); + + $this->output->writeln("Preparing to Git clone " . $package->name . " from " . $matches[0]); + + $this->output->write(" |- Checking destination... "); + $checks = $this->checkDestination($package); + + if (!$checks) { + $this->output->writeln(" '- Installation failed or aborted."); + $this->output->writeln(''); + } else { + $cmd = 'cd ' . $this->destination . ' && git clone ' . $matches[0] . ' ' . $package->install_path; + exec($cmd); + + // extra white spaces to clear out the buffer properly + $this->output->writeln(" |- Cloning package... ok "); + + $this->output->writeln(" '- Success! "); + $this->output->writeln(''); + } + } + + /** + * @param $package + */ + private function processGPM($package) + { + $version = isset($package->available) ? $package->available : $package->version; + + $this->output->writeln("Preparing to install " . $package->name . " [v" . $version . "]"); + + $this->output->write(" |- Downloading package... 0%"); + $this->file = $this->downloadPackage($package); + + $this->output->write(" |- Checking destination... "); + $checks = $this->checkDestination($package); + + if (!$checks) { + $this->output->writeln(" '- Installation failed or aborted."); + $this->output->writeln(''); + } else { + $this->output->write(" |- Installing package... "); + $installation = $this->installPackage($package); + if (!$installation) { + $this->output->writeln(" '- Installation failed or aborted."); + $this->output->writeln(''); + } else { + $this->output->writeln(" '- Success! "); + $this->output->writeln(''); + } + } + } + + /** + * @param $package + * + * @return string + */ + private function downloadPackage($package) + { + $this->tmp = CACHE_DIR . DS . 'tmp/Grav-' . uniqid(); + $filename = $package->slug . basename($package->zipball_url); + $output = Response::get($package->zipball_url, [], [$this, 'progress']); + + Folder::mkdir($this->tmp); + + $this->output->write("\x0D"); + $this->output->write(" |- Downloading package... 100%"); + $this->output->writeln(''); + + file_put_contents($this->tmp . DS . $filename, $output); + + return $this->tmp . DS . $filename; + } + + /** + * @param $package + * + * @return bool + */ + private function checkDestination($package) + { + $question_helper = $this->getHelper('question'); + $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 has been detected as installed already, 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) { + $this->output->writeln(" | '- Skipped automatically."); + + return false; + } + + $question = new ConfirmationQuestion(" | '- Destination has been detected as symlink, delete symbolic link first? [y|N] ", + false); + $answer = $question_helper->ask($this->input, $this->output, $question); + + if (!$answer) { + $this->output->writeln(" | '- You decided to not delete the symlink automatically."); + + return false; + } else { + unlink($this->destination . DS . $package->install_path); + } + } + + $this->output->write("\x0D"); + $this->output->writeln(" |- Checking destination... ok"); + + return true; + } + + /** + * @param $package + * + * @return bool + */ + private function installPackage($package) + { + $type = $package->package_type; + + Installer::install($this->file, $this->destination, ['install_path' => $package->install_path, 'theme' => (($type == 'themes'))]); + $error_code = Installer::lastErrorCode(); + Folder::delete($this->tmp); + + if ($error_code & (Installer::ZIP_OPEN_ERROR | Installer::ZIP_EXTRACT_ERROR)) { + $this->output->write("\x0D"); + // extra white spaces to clear out the buffer properly + $this->output->writeln(" |- Installing package... error "); + $this->output->writeln(" | '- " . Installer::lastErrorMsg()); + + return false; + } + + $this->output->write("\x0D"); + // extra white spaces to clear out the buffer properly + $this->output->writeln(" |- Installing package... ok "); + + return true; + } + + /** + * @param $progress + */ + public function progress($progress) + { + $this->output->write("\x0D"); + $this->output->write(" |- Downloading package... " . str_pad($progress['percent'], 5, " ", + STR_PAD_LEFT) . '%'); + } +} diff --git a/system/src/Grav/Console/Gpm/SelfupgradeCommand.php b/system/src/Grav/Console/Gpm/SelfupgradeCommand.php new file mode 100644 index 0000000..5428783 --- /dev/null +++ b/system/src/Grav/Console/Gpm/SelfupgradeCommand.php @@ -0,0 +1,237 @@ +setName("self-upgrade") + ->setAliases(['selfupgrade']) + ->addOption( + 'force', + 'f', + InputOption::VALUE_NONE, + 'Force re-fetching the data from remote' + ) + ->addOption( + 'all-yes', + 'y', + InputOption::VALUE_NONE, + 'Assumes yes (or best approach) instead of prompting' + ) + ->setDescription("Detects and performs an update of Grav itself when available") + ->setHelp('The update command updates Grav itself when a new version is available'); + } + + /** + * @return int|null|void + */ + protected function serve() + { + $this->upgrader = new Upgrader($this->input->getOption('force')); + + $update = $this->upgrader->getAssets()['grav-update']; + + $local = $this->upgrader->getLocalVersion(); + $remote = $this->upgrader->getRemoteVersion(); + $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."); + $this->output->writeln(" Additional information: http://getgrav.org/blog/changing-php-requirements-to-5.5"); + $this->output->writeln(""); + $this->output->writeln("Selfupgrade aborted."); + $this->output->writeln(""); + exit; + } + + if (!$this->upgrader->isUpgradable()) { + $this->output->writeln("You are already running the latest version of Grav (v" . $local . ") released on " . $release); + 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) { + $question = new ConfirmationQuestion("Would you like to read the changelog before proceeding? [y|N] ", + false); + $answer = $questionHelper->ask($this->input, $this->output, $question); + + if ($answer) { + $changelog = $this->upgrader->getChangelog(GRAV_VERSION); + + $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.", true); + $questionHelper->ask($this->input, $this->output, $question); + } + + $question = new ConfirmationQuestion("Would you like to upgrade now? [y|N] ", false); + $answer = $questionHelper->ask($this->input, $this->output, $question); + + if (!$answer) { + $this->output->writeln("Aborting..."); + + exit; + } + } + + $this->output->writeln(""); + $this->output->writeln("Preparing to upgrade to v$remote.."); + + $this->output->write(" |- Downloading upgrade [" . $this->formatBytes($update['size']) . "]... 0%"); + $this->file = $this->download($update); + + $this->output->write(" |- Installing upgrade... "); + $installation = $this->upgrade(); + + if (!$installation) { + $this->output->writeln(" '- Installation failed or aborted."); + $this->output->writeln(''); + } else { + $this->output->writeln(" '- Success! "); + $this->output->writeln(''); + } + + // clear cache after successful upgrade + $this->clearCache('all'); + } + + /** + * @param $package + * + * @return string + */ + private function download($package) + { + $this->tmp = CACHE_DIR . DS . 'tmp/Grav-' . uniqid(); + $output = Response::get($package['download'], [], [$this, 'progress']); + + Folder::mkdir($this->tmp); + + $this->output->write("\x0D"); + $this->output->write(" |- Downloading upgrade [" . $this->formatBytes($package['size']) . "]... 100%"); + $this->output->writeln(''); + + file_put_contents($this->tmp . DS . $package['name'], $output); + + return $this->tmp . DS . $package['name']; + } + + /** + * @return bool + */ + private function upgrade() + { + Installer::install($this->file, GRAV_ROOT, + ['sophisticated' => true, 'overwrite' => true, 'ignore_symlinks' => true]); + $errorCode = Installer::lastErrorCode(); + Folder::delete($this->tmp); + + if ($errorCode & (Installer::ZIP_OPEN_ERROR | Installer::ZIP_EXTRACT_ERROR)) { + $this->output->write("\x0D"); + // extra white spaces to clear out the buffer properly + $this->output->writeln(" |- Installing upgrade... error "); + $this->output->writeln(" | '- " . Installer::lastErrorMsg()); + + return false; + } + + $this->output->write("\x0D"); + // extra white spaces to clear out the buffer properly + $this->output->writeln(" |- Installing upgrade... ok "); + + return true; + } + + /** + * @param $progress + */ + public function progress($progress) + { + $this->output->write("\x0D"); + $this->output->write(" |- Downloading upgrade [" . $this->formatBytes($progress["filesize"]) . "]... " . str_pad($progress['percent'], + 5, " ", STR_PAD_LEFT) . '%'); + } + + /** + * @param $size + * @param int $precision + * + * @return string + */ + public function formatBytes($size, $precision = 2) + { + $base = log($size) / log(1024); + $suffixes = array('', 'k', 'M', 'G', 'T'); + + return round(pow(1024, $base - floor($base)), $precision) . $suffixes[(int)floor($base)]; + } +} diff --git a/system/src/Grav/Console/Gpm/UninstallCommand.php b/system/src/Grav/Console/Gpm/UninstallCommand.php new file mode 100644 index 0000000..dfe37e9 --- /dev/null +++ b/system/src/Grav/Console/Gpm/UninstallCommand.php @@ -0,0 +1,195 @@ +setName("uninstall") + ->addOption( + 'all-yes', + 'y', + InputOption::VALUE_NONE, + 'Assumes yes (or best approach) instead of prompting' + ) + ->addArgument( + 'package', + InputArgument::IS_ARRAY | InputArgument::REQUIRED, + 'The package(s) that are desired to be removed. Use the "index" command for a list of packages' + ) + ->setDescription("Performs the uninstallation of plugins and themes") + ->setHelp('The uninstall command allows to uninstall plugins and themes'); + } + + /** + * @return int|null|void + */ + protected function serve() + { + $this->gpm = new GPM(); + + $packages = array_map('strtolower', $this->input->getArgument('package')); + $this->data = ['total' => 0, 'not_found' => []]; + + foreach ($packages as $package) { + $plugin = $this->gpm->getInstalledPlugin($package); + $theme = $this->gpm->getInstalledTheme($package); + if ($plugin || $theme) { + $this->data[strtolower($package)] = $plugin ?: $theme; + $this->data['total']++; + } else { + $this->data['not_found'][] = $package; + } + } + + $this->output->writeln(''); + + if (!$this->data['total']) { + $this->output->writeln("Nothing to uninstall."); + $this->output->writeln(''); + exit; + } + + if (count($this->data['not_found'])) { + $this->output->writeln("These packages were not found installed: " . implode(', ', + $this->data['not_found']) . ""); + } + + unset($this->data['not_found']); + unset($this->data['total']); + + foreach ($this->data as $slug => $package) { + $this->output->writeln("Preparing to uninstall " . $package->name . " [v" . $package->version . "]"); + + + $this->output->write(" |- Checking destination... "); + $checks = $this->checkDestination($slug, $package); + + if (!$checks) { + $this->output->writeln(" '- Installation failed or aborted."); + $this->output->writeln(''); + } else { + $this->output->write(" |- Uninstalling package... "); + $uninstall = $this->uninstallPackage($slug, $package); + + if (!$uninstall) { + $this->output->writeln(" '- Uninstallation failed or aborted."); + $this->output->writeln(''); + } else { + $this->output->writeln(" '- Success! "); + $this->output->writeln(''); + } + } + } + + // clear cache after successful upgrade + $this->clearCache(); + } + + + /** + * @param $slug + * @param $package + * + * @return bool + */ + private function uninstallPackage($slug, $package) + { + $path = self::getGrav()['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()); + + return false; + } + + $this->output->write("\x0D"); + // extra white spaces to clear out the buffer properly + $this->output->writeln(" |- Uninstalling package... ok "); + + return true; + } + + /** + * @param $slug + * @param $package + * + * @return bool + */ + + private function checkDestination($slug, $package) + { + $path = self::getGrav()['locator']->findResource($package->package_type . '://' . $slug); + $questionHelper = $this->getHelper('question'); + $skipPrompt = $this->input->getOption('all-yes'); + + Installer::isValidDestination($path); + + if (Installer::lastErrorCode() == Installer::IS_LINK) { + $this->output->write("\x0D"); + $this->output->writeln(" |- Checking destination... symbolic link"); + + if ($skipPrompt) { + $this->output->writeln(" | '- Skipped automatically."); + + return false; + } + + $question = new ConfirmationQuestion(" | '- Destination has been detected as symlink, delete symbolic link first? [y|N] ", + false); + $answer = $questionHelper->ask($this->input, $this->output, $question); + + if (!$answer) { + $this->output->writeln(" | '- You decided to not delete the symlink automatically."); + + return false; + } + } + + $this->output->write("\x0D"); + $this->output->writeln(" |- Checking destination... ok"); + + return true; + } +} diff --git a/system/src/Grav/Console/Gpm/UpdateCommand.php b/system/src/Grav/Console/Gpm/UpdateCommand.php new file mode 100644 index 0000000..5ca795e --- /dev/null +++ b/system/src/Grav/Console/Gpm/UpdateCommand.php @@ -0,0 +1,214 @@ +setName("update") + ->addOption( + 'force', + 'f', + InputOption::VALUE_NONE, + 'Force re-fetching the data from remote' + ) + ->addOption( + 'destination', + 'd', + InputOption::VALUE_OPTIONAL, + 'The grav instance location where the updates should be applied to. By default this would be where the grav cli has been launched from', + GRAV_ROOT + ) + ->addOption( + 'all-yes', + 'y', + InputOption::VALUE_NONE, + 'Assumes yes (or best approach) instead of prompting' + ) + ->addArgument( + 'package', + InputArgument::IS_ARRAY | InputArgument::OPTIONAL, + 'The package or packages that is desired to update. By default all available updates will be applied.' + ) + ->setDescription("Detects and performs an update of plugins and themes when available") + ->setHelp('The update command updates plugins and themes when a new version is available'); + } + + /** + * @return int|null|void + */ + protected function serve() + { + $this->gpm = new GPM($this->input->getOption('force')); + $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; + } + + $this->data = $this->gpm->getUpdatable(); + $only_packages = array_map('strtolower', $this->input->getArgument('package')); + + if (!$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"); + + $limit_to = $this->userInputPackages($only_packages); + + $this->output->writeln(''); + + unset($this->data['total']); + unset($limit_to['total']); + + + // updates review + $slugs = []; + + $index = 0; + foreach ($this->data as $packages) { + foreach ($packages as $slug => $package) { + if (count($limit_to) && !array_key_exists($slug, $limit_to)) { + continue; + } + + $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 . "]" + ); + $slugs[] = $slug; + } + } + + if (!$skip_prompt) { + // prompt to continue + $this->output->writeln(""); + $questionHelper = $this->getHelper('question'); + $question = new ConfirmationQuestion("Continue with the update process? [Y|n] ", true); + $answer = $questionHelper->ask($this->input, $this->output, $question); + + if (!$answer) { + $this->output->writeln("Update aborted. Exiting..."); + exit; + } + } + + // finally update + $install_command = $this->getApplication()->find('install'); + + $args = new ArrayInput(array( + 'command' => 'install', + 'package' => $slugs, + '-f' => $this->input->getOption('force'), + '-d' => $this->destination, + '-y' => true + )); + $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"); + exit; + } + } + + /** + * @param $only_packages + * + * @return array + */ + private function userInputPackages($only_packages) + { + $found = ['total' => 0]; + $ignore = []; + + if (!count($only_packages)) { + $this->output->writeln(''); + } else { + foreach ($only_packages as $only_package) { + $find = $this->gpm->findPackage($only_package); + + if (!$find || !$this->gpm->isUpdatable($find->slug)) { + $name = isset($find->slug) ? $find->slug : $only_package; + $ignore[$name] = $name; + } else { + $found[$find->slug] = $find; + $found['total']++; + } + } + + if ($found['total']) { + $list = $found; + unset($list['total']); + $list = array_keys($list); + + if ($found['total'] !== $this->data['total']) { + $this->output->write(", only " . $found['total'] . " will be updated"); + } + + $this->output->writeln(''); + $this->output->writeln("Limiting updates for only " . implode(', ', + $list) . ""); + } + + if (count($ignore)) { + $this->output->writeln("Packages not found or not requiring updates: " . implode(', ', + $ignore) . ""); + } + } + + return $found; + } +} diff --git a/system/src/Grav/Console/Gpm/VersionCommand.php b/system/src/Grav/Console/Gpm/VersionCommand.php new file mode 100644 index 0000000..231645d --- /dev/null +++ b/system/src/Grav/Console/Gpm/VersionCommand.php @@ -0,0 +1,92 @@ +setName("version") + ->addOption( + 'force', + 'f', + InputOption::VALUE_NONE, + 'Force re-fetching the data from remote' + ) + ->addArgument( + 'package', + InputArgument::IS_ARRAY | InputArgument::OPTIONAL, + 'The package or packages that is desired to know the version of. By default and if not specified this would be grav' + ) + ->setDescription("Shows the version of an installed package. If available also shows pending updates.") + ->setHelp('The version command displays the current version of a package installed and, if available, the available version of pending updates'); + } + + /** + * @return int|null|void + */ + protected function serve() + { + $this->gpm = new GPM($this->input->getOption('force')); + $packages = $this->input->getArgument('package'); + + $installed = false; + + if (!count($packages)) { + $packages = ['grav']; + } + + foreach ($packages as $package) { + $package = strtolower($package); + $name = null; + $version = null; + $updatable = false; + + if ($package == 'grav') { + $name = 'Grav'; + $version = GRAV_VERSION; + $upgrader = new Upgrader(); + + if ($upgrader->isUpgradable()) { + $updatable = ' [upgradable: v' . $upgrader->getRemoteVersion() . ']'; + } + + } else { + if ($installed = $this->gpm->findPackage($package)) { + $name = $installed->name; + $version = $installed->version; + + if ($this->gpm->isUpdatable($package)) { + $updatable = ' [updatable: v' . $installed->available . ']'; + } + } + } + + $updatable = $updatable ?: ''; + + if ($installed || $package == 'grav') { + $this->output->writeln('You are running ' . $name . ' v' . $version . '' . $updatable); + } else { + $this->output->writeln('Package ' . $package . ' not found'); + } + } + } +} diff --git a/tests/_bootstrap.php b/tests/_bootstrap.php new file mode 100644 index 0000000..9aa3f78 --- /dev/null +++ b/tests/_bootstrap.php @@ -0,0 +1,55 @@ +bin/grav install"); +} + +use Grav\Common\Grav; + +// Register the auto-loader. +$loader = require_once $autoload; + +if (version_compare($ver = PHP_VERSION, $req = GRAV_PHP_MIN, '<')) { + throw new \RuntimeException(sprintf('You are running PHP %s, but Grav needs at least PHP %s to run.', $ver, $req)); +} + +// Set timezone to default, falls back to system if php.ini not set +date_default_timezone_set(@date_default_timezone_get()); + +// Set internal encoding if mbstring loaded +if (!extension_loaded('mbstring')) { + throw new \RuntimeException("'mbstring' extension is not loaded. This is required for Grav to run correctly"); +} +mb_internal_encoding('UTF-8'); + +// Get the Grav instance +$grav = Grav::instance(['loader' => $loader]); + +$grav['uri']->init(); +$grav['debugger']->init(); +$grav['assets']->init(); + +$grav['config']->set('system.cache.enabled', false); + +/** @var UniformResourceLocator $locator */ +$locator = $grav['locator']; +$locator->addPath('tests', '', 'tests', false); + +// Set default $_SERVER value used for nonces +empty( $_SERVER['HTTP_CLIENT_IP'] ) && $_SERVER['HTTP_CLIENT_IP'] = '127.0.0.1'; + +$fake = Factory::create(); +Fixtures::add('grav', $grav); +Fixtures::add('fake', $fake); \ No newline at end of file diff --git a/tests/_support/AcceptanceTester.php b/tests/_support/AcceptanceTester.php new file mode 100644 index 0000000..4c7dcbb --- /dev/null +++ b/tests/_support/AcceptanceTester.php @@ -0,0 +1,26 @@ +{%A=j^l3K4nx_u^zzze%OeJ2>HN`M!9c| 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�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

xa_u8c)hsQoXBakm44Rj@Hu~WCdpmw zLBxM%pu~EGswmC+EVP)56JZJpu-#>nk~SF8&JP@Ra#;~2?|%ubLu+2dd284vHhK>0 zvL0o3g|n)o(FlQOuN7pLc!J=k+@FFcuhcQS2eXB|f|0bH6 zx|!d(j5c|C4`ih}E0XH<7|<~UvjHtYec96NTWEldr*vSuXKgOUuII@e#w(AG!Yz$! zpeUrrc@9WAbv8BYI-}V_+Sk^@<+wj4Qp=ySrD{oic*2rjdmyd|)8b8%{J%)H}t}TvD-QAg~5d~Lg9fQzM zOFiZ)u1)Fht>%Qvc^z9YmfnV`02yzjQA@nW9w`1olJcs8P@JQ59?vWMH@ZNZ+h?WyFnLOr)J| z4D7Jvw4K>zai~rlTe-05&dH*rM_PUI`Jh3H`OEl9Ma^dQ4p~lmGy$Zy=h+rF_R3hI z&aLxsoEb6?8S!(lJ`}l>Wa;}Azpn4}uk#FgcP3}iNjl)#eBSQ(Q zyzyq8Aj)sV+EKr2%bE;ilGQT{4C}-e5#v7$K!i6}xDIk2c>*M8VTtA;_li z_F6_+MTx;-wk3~Mm|f983yq#$6^OBzafi7yn^&|0)zV2Gvf>)&nQ2?Z>7JVFc(8rQ z+n+nKk|8e-RaR=LhKq&3iD^~Fiw+G-n;^Pv_Y6BCxg?*K6JM$Od;}YV5y$4VqTDic z%A|GfM>EENKg0{=)@V}HSYG})mu|C5O>)O+cr@Fg;;ODs5Y%QoOd^BbF3>>NQ!B77 zb>x-zc)_Jp8go&CsqIyY)({P3x1`peIEpTP7KcEQ>bd|CWX_4wg@+Z( z-ciC8{a2I>hoa6m+4e*#_kuWdaz0K(N&Y^vn_Pwac(tY7{*ghM`SpRbEaS0CGMC{k zZ&)FnTL16{h5k%%fRY40<~28zAhN%^b+#A!FoMV6fNv5ax=vKO9;RMjP`2@mZxe*= z^O3LSVHOekHMM!|l)A-&X38jrRmz*Y0McIC}m&IW=?1_yxT!+w}1vbkkX)gIct(}R}GjIxc)QNu(_~=-sDIYY}r2klI zbpP^`%z1=CZ18;2R!Vt&`p(U*R3So>hqNjmE^TiV?gpJRc>j$Rmy+C-Xm>V4#nIBx zLOOXmPD$}(-c!TNgBItJFz7@H<(b`qdjjla$^&(D1 zO`DZdLB!_7rTQW98Shp(VV>X~w;q;Ckgxa1_c!nlltZpctZamI1LDH){&4$jJ0YT+ zd;U3X7hc<);y~7P>CnrDc6Yi93dQUGTEi8k46l&9;kTZxXfd8`MQV%a7d}@@$s>0b zYFJo*?t^+BL26cQOf3S8#X^cv<>#0cSRo}xUFEEVb>ywklZ5aB{NL01PWvz zg%dyKEbntd$&Z`?8($ZcE6M*L7EbR5%%L5VJLT%42Qr?rE3E+x(|yLsppGe8RE}n? z$|M@8W0*q4NhwRJ`D3=DHq9{JR~IdRTuS|F+(c6-SPRoxw#2=>2IE3U*-rs&VoM{0 zA)^Z6`x%v`=AW1Q9r*Hvx-g0ZKfM*yk)Fu>cZ!; zv=TbO%^geVhRzF~7DCxa9AdDVr9xAtNW)o%RpX`ZH1SvdZ|D%7W!f7#UF>zX89ePhc9 zGD8QnB@q1?w@Ig4!}QoKALP!x>7S~MC_2T$47a#nXaPU{`UpONxlUs|XgrTB*OP#J zdeZoHnb`*PoeV*7QSb_y;C)7yV!GN zs(Mz7yd58RJYTtoVnC^BJ4-3{Xi?~#ZRkg_a?AX25(pIL9r@OW-!c2TDZFI)ro?p) za}(`XF3rvl0B%6GvdnM$d|c;Cr|9fKZdM{{W6XAuOZ&2z6789i`5(k!;e3jtiYy=M zB;~=HAe~3;i6tdZ8dW%{3Sl>V3dPW9p$_yMhQ zatbnGe4_9LCeix#XCG1R+#U*B^uRXC&b--!zu38%gZ#*Ej-}L&;24V7a~(`tsW>Gj zrZ2oLn;1nnV*Q8V$<3uz_Ug|)fOeM zqZW#V;bJQwkDm*pxTn@#9V1z7bQmJ`Xe#8q(RS)~WiZEtPJAS$0is8Q+xRMEQ^EoW z2}=VX7r!c!U#L5H!&XxA+q&S@(WuGtA9b%fh)p!vz5Tg5U>t^8a(X#gfRoP?k!tVorQKShmAp(CM1$}OB6kYRmx=Sr)BGO;~L7=^z zdOqQ8^&51pA(8P}O$5Hu{&ZzEv?*)HiftwIP@G~r;%PZ7DcD`YVCfLHQTv{HUJ%a_ zm9&=orOEa94)#xFO_K_GDpu0gqZGGK$bAI9=dd$wVX`tZZ@-4TX)RK`HwtM(Wq6{| z_Gs~rC^uXo14wgIY0>IVo&MagS&I=a$tcCiiYHMQSFYynpg!_=Fx|nr`?PdZ8e11# zLA7?O@kx7Kl=sKNY@Q##mHytXKonYraTyd-@}UM{@Ybgrsm0ne4^>Dt3y&J(96hCIgP>ntOk zk|%h;H24UR#ucrE}ez}(&egz!+v-)Z}${TF*TY_SAz zjaK`p1H%^-s~JB(uk$40FGNw1ySzJj*6J-}{dfr8hUUop<1AE+NilXwojCrGuY&wp zci{bzRjac?jo-s(hNBq0w4yZ362*uWjG~J1@ojuK4f9hgAb6Oskf_(xdB$aU(9G!g zQ?q0y{GZ6%TB1LxImI)o5Z%tSVx-Wt9^KzxsV@(hqpU^h6DzH~QM94g@g7`U@HVqL zVJjdw{(#RS{{z##Eqtx3gZW2hi)jnZXH(a!GwQI>Rk!F}m{=og4w{)J1>_S?1#A8H!+%L#cqAVgz0RRDkhD_ zI6={n1TODNWxzID*YpaBhNovrwJ-sL1vb0hTvfpab=Dt-aPK)5G^ZO_( z#r=&L$;9)Sl{WicXhL6j8?VWux%Nn&6qgdUby#~lTvfC#;F>E%qnX>>7G3IPLz;Xy z@@PpA+F^q*1$xZ7E3eEY2;aMZQ|Jg}v@7}b6-?f|4}?h4O12!?5^0@C+S=RG&oK1e z8&ev@qQb{`EwGWca?F*CDxRfWX(0vUyo~Qb^&oo2ds~!niSp;A(1HeY38EU6K0ydsxcj{!+HC!72rCqJd09&D1txT%+;!kBh7aPxWqMt4Jvd z{&WmtH!swyYtPCK&84o=g5Ijrs4xjmYZpQj)2mq_{PVnZHuqCrSDK&LVBtD*(E6r^ z$)a+IZI2bmv)dYJ&Vl&GS2gJdjw+H&qu;?RflffKOqAn|a$!MIRIXMMo6K5H>;Nt_ z2$vA#4}Qc^NS`pT&CCi^y6kdz zuJDl6Z-*<*X^)ysR2arI?!+8a=j~wC3|?uciUUts$oM5WGG`=a1Quf^+IDerCWj&v)KlB?(zVs=!DE?$nVg8Lsc|iTcrl@Wo%~d?<%&Ux7S@^>9N5% zMg$qRoI_G8%9OIa7ZMX}WYiiY!3);k9G3fS=p^ay^_SvFJsyqz^t~ob77|>Qbi3zY zzJc}=iCdcud(ws!M9|km_e8R^Mv_0@_2~yZ;bGy$F3_A_3g5>i$A}1gUm%vx+L_qi zO*UgzG}4BPB`_uGMA?F-G`1c?W21Gc?&w(2ZxAdoqmuUKCFdtYow?F3aqXPO#;lkD!oh7N~PXLm<; zP^Ik;F*ejdrY?4aU(UyoX=$zzw$j*q)*ulwT!H{_WtFl1IBocE6QKV(NE$MPY5(3G zJ)Z(2F`A#yhw83Z#Z_>SP*2Q|LJ2TB03kCr-?(Z`LwX^SXjezVn?pt>pea)TWF@tj zwXJavq>TrK3)}C1-g^!YRZ&_Zz+-plln&qH+b3p3lH=fjvPZ)sxWGa(3bZrXC_n$&xBHete z`7n61_Jlm2UJfU|o?KY4oBNxweYWnSd@dNQ)aor}ndVeozSQuH!Jgz?4xAdNlccM> zUibZPHfLwC#?P@hjH}}sX{u1bhnI9!bPSps2h8y(;23$&@H5$y!*_ly4>@ski8~Ft zB@tqFfQ2KcZ=n>ob0WQuUGAfzg`CZ0l5yzIScvEGR{`#Wa=m2jT904KQc^>bABR08 z)C+<_WOl0`@A7blMOUm@9iLg>Fu32|BpA?FF!h+y%}qJsy}I`&EFd@shT9XcU~eGx&b`gf+ul9%vPzC2ZasU%JzNrKib5p?bkqq$+|{rO6ejNfoTgToueEX zTNVb+t}1mYVd+Ru|1FL+u(G{Q1MKw zrIO233phmuwGZy6Gz)=WKeU`CeHZiIlk`Y1|xb zfjwbFn{+=;oTq$DQLT3F(mJftD7DLZr4}86j@c(eMN;eyB&+&O*k9b(v=3QgHRez^ zl2-KVr+iA7JL5SOZ7QgrJ!CK$1sfG5Lph7v*YMFi@Tg0_n_GHNtKE!yul~zxI*9xM(^Gs( zJ2Y5}83qF9?p(DjPlpHdFtlf6qR)nt{I+tN_Q-J8L~Pb-XynjtWZNU$T(6YboKFP1 zF7kV*AK?7k<(nk1KC}aK@P__*<{%-xQkad%&o`Q8ft2YlsmZaF9;TnUDw@CU_T~~J z?hDn^+yx2vY=L zxpb$jTEm)C?F|ZIQK7pKNvoS#t%sEks_~t1%cABk(qFF&cXQE$p>UNsdkH(u!X2V6 zw9NT}z3WbRQWXkQcRqdcZg+XLs7Pb6wlaCG6Z+yvSJH0pFG;yr?Xfbl!0g}|x1wEn z|95=?M3Rb6C;SBZ{{R9({l5BAt3FFKjQQInw>C3KkjHl+lqqNOW8*zXHItoBiKM+v zX|*R~cuK;|M=HlOO24~D7z01VM?!PSCa|(@_p>{1B&?Onuct(l+O&4N!@K#f<{#ZE zoDAcxc{EOZ(Q%U{Q%Xt26t1+-H3>hj3B^0d8uu^%Sk1m zxcR9K3aWB=CX0^Dp?9joYq=x+^uwqJYu;)N6t-<-PqW=JZqo4hhQswgpC)hlzA@{E4{{Vp7gZ$V~ zlI|CmULwEqB}lm7tUvHt+YRA_XrVF3u=q7Q+Y0#deh<;O#TFON$T*U|e5&cr~d81>d-mr5rE_|2CI5_Re{VS_XL=(p&)3mF-YR&H_ zxLL$+<)n8^$9^zR&zz{{x2s+>rOR))#+?^9^D}cxOHDTK?2>8AVS6a~7)oSWm(d)P z{{Vc~6=y23=Y+18;B?dFgXNO5HXPS98+)5H3nL>3Mk?c>9jjE*A*<+bTWbqZZY0zE#Ei4WDyGmm0f{U5F(W_iR@09)K4;MK zvGC=ZYpIu~U7?(rV*T#o2`8VS{{TE zvy9whXza>ySJb_uYqyLcgg8rAlX(Gi^9*sG)uP0>dyymA-`-@u618cTTL|HkFgp;= zok|xt1|$Zy%Eyspdn;+lcCJXu(z05bI9(c=SH#vA5}VNSZ)`>~Hj&%h zR<98_yR~#>$eGkz_{u3jX0}qNoTQyArj1xujpE7VX_mj?GSQSUmT4np87s4Q@~dqv z*!`R6T(sBptJ8=tW63xH{!INdM(XI77Pm&8)97gm#cQkkOEhMl;uI^%Qy>lbW}TQ? z=3TdnY_9Ge7}_LY309J23f_Z0xuX3C%15O5qT20dd%K8WMREam0|(RIrZT&*+$_lE z@xGXd#wJ%Gihx{X``~t}(QYLM&ph!zjqjw@nd6F4Y!At{R@z1|KpFgfiLPqTn?&qr zmnt6zif(oNK20xExxE(AlOi3;vM(45oP5I`y@{-yNh?L{3&`gd_-VS_qQYC9k+yA~ zInLzgJqNcORZcgLx+7XWaxFsIX1kGE>fwBbX9@y^0}KEI3~(~t`OjM9t5URN^tjSn zomGahHiM&>EPlqNfM;nJ=RZJrJ^JRmFbWcfGP}5?54CeXUl(hQxJ9T*7(5-I0r(7Y z_*bE1XMZD|p7vWiq`WAyIAgfVp~2h;CnvpfV&LZ;4Iu39NhYmfXDS;@u`RG%G)@WG zp1_=En)3afPKwOZ>FPl8>H3_qBuvP%6$2RG><(}`_Z3sARyJ~56Yi~vZSEn`u1&6P zUR|J<+8H?+1CHbo-m~|g?;Vj%C95oIvDj)Fe34y7#(jtE?sC3cae@wcUZjuGs+J3ftt7Qn$rMgA(7~gK(boV(K&(^#~uPeRTmojL~)@FD`tmAMJ z0-<{a>~Ytc>cXhYnjMUjcF@O~>4QdOkV@J4Se)_re_HKO(#V-#siO_ULf&Id<*bNY z70w9u3^)=&C%rP*Pxvw2} z)cRW393-hHWzDAAYX1NaJ&nCP#(sU&R+pN5$uylLwgyzrGC?1BVG|Eod8x5Qf%AX|W`B*tOg8RN09MsP|73QXwi=G==KNBg{=nV1i)d9`C_dzvSuj(@~U9lgTE<&dRvr#yA- zUX}&9M)xtB)W?QaPzVKpIT<`xYgZvV5A)o%(S>43%8XHD8jpZSv~QDR?g5KshCRUR zQ!gQ<+|$!1Se8i&4S=7Ve58FVlDsda%_8&@Ql3lsL^kUyfIth3yBu@Cs-qPRSWDj6 zmrv8tGK>e9t(R9gD~`vWDl65or8sD4Ii3eYEP^P{AOX3L)btg}?oZ)bn)}Og+%p*@ z3cJUayRbR~^{H`bY8=;k8rpre%$D0^z$3p?`S-1;w4KZ4ax5*O4f6s|s1?@db)YJC z+^%P}`(j(Pj5jXr`QzAqXy$1X?(+$8<)aVufI%6+ImhKrGSgHkD?J#|I2O$(n~1{% zX9K^d)|WEBnDw@U;Jt}KRbt}=kWV$7+bS~9Pwg`#US*;tz;MjKf8&HP*VIrIad70&7>uE?v$ zxVLgBR5B`uMcSLT;j`3d6)=*Lc4W$?qawI+Ns*WRoE(GrdRHvGfjjEQT)`}ohk{fR zFWy4LbRM1Sd_8L`NKQA{%=;C?tfVrz-JRPr_=@iK&{nyeqiqbU$)~h}Mcoq|V{q%w zdf7P7QyI3jMgVa9wpp7w_N|Of2a?7ag97&%10ZK7kGg$1{VO|3qOOtL*xxnuaXir_ zym6q~0|(v5JYa1V=TwI)v`Pka0li3*F`FEirmVrM<=n7 zb*h_1NFugWammXQ&#zkEH2Jk|=RB%%yCh~^Y7$8<2>|Djjs;SJOG61%a%622q>>h4 zyN{7%3Uk=fy~sK}DoZU7MtN=qXP-Fo&i&Zr4^F*n&#UcgRvm*Vk6lyd=h$=s?a#uQjYFN2wgn_9@ACcu+gbFj8<12n739 zv85Z?A>E~^Qd^l0cS`x`jmPyh)fzF5=!^C#yoG=r0O$uw*h4$}_MkQ_3n}m!&_t)B zO2I#$S}e)uTZfZuhxeCT{h^GX)v^6((RBpd=pmNbN%HN>4_REF(Q!*avlL%kDE-qc zU-iiXpUiXm(aqSdR6`_>D{hh5+trCCi?_IPrLfwPgr*^B08jxy%SgLW3dOs#zMOet zknZ&**j|(Xl)xwfX$%bm3IMGj21dxPs$U7_VtCqE_JvHt+qXn6e8pXEinFy?(wqQTscCTnT4(`29@$*_M)Ec*|gEy$*{ zxVFKb-dKU|=s^7GdX;qcJFOgNL9plkSpNW1UM+v(Gf3=-9CCGnB6;U6fUlIqwkaZK%aQ&>)>CI@&Ybi}Fb}+1OWsSnjs?3t@kz9S&>5uDPb!t6~ zBcs%o<(B&5YgE|p9A#qMHvGi*C%FE#bmr;78OdHoInr)c%3IczRC$es&j10QeLX96 zh>WJ7koj6yESu==V785<0_Cy>2VC^YsrF7cm9!n#Qq_i~Bo^}dtGK{&NC%b&pseRg z@>VHHSY%T{cj3)iG5hH4Z>{bkGX3C=9wI%k-e0wMNlC|ZC2MSM_m|TE)p$5G?gMUBLl%BkU90nKN?nP*@8!$>KFEY zE3}5~6K<~@xR4BTzwI5o^z|mVD^-P>mW`T4S?*W1x+2a~GlmxAjIkgBKhIHG;Gt3r z-M1++v)t!Dw&kAb*yqb6kiBp*+O)*``V~*z9USWfVpNls1UWsuE0fvEPR!Qi@#{9x zEU`@rNeeF_j~|UxXxd6d^W9jfX=e@Apuc1JBHy@%KX=p&)MBcrCQ3%cw*uZ{VIs`2 z#MzDCDslDiT(r5AdEoVXnxs!F#bY-tNCyYZ=XcQ6`&m?tnkv@Znmjr)-u?3`r!8 zndws{bzn}!H@7!7SD$D}70{_&jKH3Ifl4-C7NVZJ6ebTe!drzfEAtz4$D-hN_oXVg zUg&x0%y$VoFu{GQ<%AaU4NfY1}5^gKTc*m`DP_H;gmYva@lIlWtyl*wUvB?^oe1j&t zqmP4pjaiWxU70$a%b^j>>QM8vuS57&@}DyDA1$4kN$r?^@IYD?0Qc$aO18@)OK9$Wm5 zG`P~<{{ZazgeUnJQ37jb5lnX zEAupM334-rxPk3%9vL>4LyW1&KH%4_87V8Hx)V}b5$2h97{eUXMsi(E+bujnZ@9{1 zykFilY`m4nBl0<{T~21zu8tb5$(L=frLba?tS(pYB#DxzJbb3Io*7hL?uJ)ryC;>b z+}ow?tcvb18W+h@INAp|=e9nT%Nlhs$?`wHsNLC*s_4&TT1X=;YMh@i5bDI^47eZU z*KHg^o|As@Ef897%P!&>zEvGZxUD7#`l3xvH4(990l{Ip2`s8ca0h;WLtY0l>MDBJ zw{1KChEa*1{a9qd=c)X^rCe;a>St0jdOf80UqC(N#h65*GyUcwwFQ}+s#bL{3P_p(w$0fTWm^bwz})bF62Se03M%O#o-fH zY_5B3Lw|PHQ16~HNjw_Xl_dvgvSjXO@<1mcbDVb-(Qy)FDfyM%9A#LL57w1~)Uc@# zS1MZ{?RtBPhTY0U(HS97VNhF~;jnS)M@r6VKZt&Ylx=ZmZjvmC@@H{WDoZzFPgD5{ z!MMrTySVC^7M%Y8X1OUUNbex%obDaTrCoDI>6K1NJxkYKT$c)i<|xDWV*x zrMWAYA}S|^VOK!xS&)qJ>x0s=p3Sn>yBbk_q#PvR1sU200R9y;#>z;)>cO+9n+zXLn$wxm~wINU2SwY@2T18(5>Z+law)?xQ#QC z$~uxN|sYQIdHgyjW&Zs~_J7 zf@rB}N8K@#eAXwr)K=Cdn0(SH&ONJ^od-8(u;o5fX2ar!mDpV(JRp_`-nrts<$;P` z?&d19dz)4__R`}BH?n{LZif}aT7=`fIw4XvF0}GXAdP&Ia!WSiG3}1^idNKZ+~)Ob z$*iWBdBKA=R|Il95ni4iDrsnQ)RRo-8tZhRkR3C-3^=atO)Oz@S?3aC_ehR1Nmj@? z=nW{w-A343GrY}1VA$wTY%pWw3j&q*%czDgH&U00ole#glFNNbQ zhBR4@cd+As*1Boa%2;-HmC5-Nl`>X4X}PEQNRZSDw9aD+s8m zH*xaH#)ga%DQ5diBJEAl#v5|yrf@5t6Lyy=q$0YVO@f3$ADq%ju;8e`2OaCjs_yP| zsM}DxnmF|Lo_NCfAhBZP?&tvH6{Z?b+9<{DUq33njxz6An&L;2IODf0+?db{b&t(if<+yMphx|5{3OoC-b8HgqLyxNjW^9T3eKrf{+P73qZrQA%W>a1{5(M zpkY9)Dvitsy(aW7D)LCTSRRFipkY7>OhVEaqJf~GVL*zc<=g9Bl=O-`y1LDUSr!aX zJ9Y{MV5A9X6b+!@^`LenhT?mF^%F@Jf4JmTb3W#7D|;Gt-X_%TkyFfyXw+aVkq$o* zgILauDg06W0Vyq+t8e277y)%;=zFv;xPC_m^{#2)VE!3f)URnZzNW3Vuch6bMSBvl z{uzIEe_lUY=avq#*q3mI+5+~SXA@j&mOp1?g7Zz5NQ(W}@?9ku`gu+H*F`zW#_5x5Jxb|y2A^#O-IHw# z^6gR8yEZ-RkCs!ZX>J>AT9Z~`rU_)pj&~|oug%uBqc<7JUr=$;GQ^j%TgSEq5-!}8 zC!joWTjBANy{5%FYjZhn#oV&2(zIe=0F^<jHBCz(n{_|RuYY%(zGwLsvO_%UjzA z3vnANleHXjt&ct!m1ITFPC}FI(Or}dDPpe+#cmU47)u_3yj66%{1|jB0E3T z`g4r->08vLIIGi1cM_Jmn>vM?+{0{QkzHSs3C0H-N%rbL3c{66TiqJApHbR$i4;-^ znH3`)`UB~jnu)nCR8r9dernsai6gT8;1KoAdo*iNPm(*BN;2J*QJ)!T*yEp@sWoy* zJCzrG2gX^!cE_eSXRkF9lY0#$N2pz@M3JONG7Kq3$Ky^lEVL!XTy>~cfO(R9#B;dy zsrI+MfkNAvTEs7BB%%ns=r9O=r8DXIP@=bcicz-4FrRrpx+R~1w3GN^hLS`VujYyE z5#@zak$E5PvxnkTB5^rN=C4xl@3S3Vj zedf4qx3KzgT%5HpdD`AZBE8z**-Qg9!~wo#gKqKb$DpktDsx>QnBDGOp3Ci8WGOw2 z&JhonI0_YV-2L8joQxXI6**?@FOhVmDC&&I@Wibmq$c5=lXl-I@Z9Bd{cEY#w)-6p zyt)usXqOFt63qnYgq$&qj&MloJw<6J?&wF>B`wiz{x`e2k%r@Q0`3kD454w#k6d*h z;YyTTNpZ5VLgpu!tl}vfa^#$j52yL{t>bl}lUG+Z^h+tO3A9T`o9!+(#ImmTVb9Bs zLHFs!b7E=Ac5&Sjv@~E{PjtRbmBs7`Kq&U|2j>{=oOJv}dCrc`O3&~gTNJe`31Zdl z<(E&ngvr&KHv(49NX9<*J-XLc8Mg+TDqe@s;k4FOGTnlarr(*)dE@z4j+{EZ4_VW% ze|9>t`i;_CX}3C*F3lu>#Q-i!?qUD{cILIeH@uEn-kRi>O-~fOWzhU>4fO3D<^7e& zXpnuttI>`RB=rNfa%I_0MNE||X%tL4X`R$(d)d~`lw3!NV zPU#KSt)Tg_}3Vi(fA&zBxZT%3_ssmV5y*v%;2A+Y#W=DLm~xQ*6!+YwT5ep1Be z&;VEwXrKbd4e{d2+>LWx!R5 zdBN&3&(L~THRa5lnX5@a$k41_=f#?3tgviZB@Z$%B}7Z}F+B1%gPQ88HSF8H$fXwN zW@^Pfyz1d%UnsdNxDI_i`d6JO$vfQHS(j|Is|oeQw3R_%#z|O$3MgI74}1U!rsY!J zMMg1i(1^=svu{{ak_v7aBhcryX5mq;pwusNP`-576YR;)%t76g&pm1zX=;m1uc=)P zHWzO-V{lK%tDVNT!@<;cu@!3B92B#>(lm~DF(jPVwHTzVjt1|zkfvYeat1R)eS<@x z({*XI@wySY2>_NpmBm_uslMj)=Ha%*ZCAuI&g}#&kiuIyiFS(Cjv6snda@@`vmPnR z&LkgvavRY4*F%c6jP^I9)ow54jjk>N6&Tz`ey8hP?wW%4Rn1jNN$SHMexWSa*TQAa z6su!#?c1QKZSvX?vno5@Ov|~s5X~aTBRI+V&wAc4Z6?u^dXz%|bIAbo99Fg@-|+m= zt?E35R4TXLZiD)oMwcXiq;=ipo^v_8rpdXUipF?e!f00Fm{tQv-xj zO07kyn@;{mDDFBj-H+i@!2E01V1^rq18QP5{{UcPKU37vai!dbr=Y-4fHOz}i;P1> z#KoXtO5hX>DF9-Cu(&i_e5^2NxcON$Q7~ZG;N$3+@azcFM$#AdSQM{=Pp$E~vw z){~kvjuuRJ*;}4^Quc_isz`9NV!O9e_PUm^GI8d(<6!tFG`+Gd<@2@fH|#R@r|n`# z)GG8CZDwApRqY~X@Re5RZ;>ze4L*30>?(FB;N+4!*K}Tp@uZ#G7Oa4zF?&z~Vw(gm zIHWM!K*YrY3{VJ41z^T#C6n7;U0F8kTbZFh^v%d0mr6I$iNPB7{w>raz@9sMfu2hy z#{AbG%CMdqjJJ)w2D#SE*S7Iyohbr%ZY{~`a3oRqj33gurFTH<}Ue-%=YTMsf zUXU%Xt>k0;A@;ZA*fq^O9eDkl`f5~a$Ei7q(a!ZM2;(Ca%_VIbG)+BF#!^3>2mwJf z>;ka)N%ZEY$YSm0nqpy9aSW2)3xcL(`Im)oaKL(GcJ`}MZE~|YMHRIzKT^53Lt$(# z8rw5S%gX_ioG(%^Msev=O+pfU`!fmCmgtS(&9|E5TF7?e5zUy-KSwoG z(oWAo-%^c$xsA<)uba4-p~8$IQ-;s4LNF@|(TZ+XvIyvCO7Yy-P8!u?KzAL$sV%zz z?Z+K!nI{?UTDwK7P|V2;jJ*kG?SYP-_%$zkqW0Y8F0PDnOA@ok&R2}%o&|a^h1JX( zHLP_gZnXQGXN~;K60@9TT(BhL@W#xi*T z4%or#PI8|$o3aFq&k|d-0@hRKsgX9a$slkT0o-&r>M_N2;Hq=oX{X3jjkhEI&5B(< zIpes*ikqK#;1ke(IITUbl=)g0Drl>1D%-=Hkk9jOAH!8qIWkiD{O3||vRon>b zN7LBW3h}6}Uqb-ukx0b(Q$MM1>0Zp^DJ{$siY;5u4)Y=-0HZi3CpFI~wba%;ta3YC zxniM@KvZY+sGpfhYp}`EWN<K0Ku)mUNEo3SIwA4Zy73(O8%mf)W2sFs>u3{ z)1)&GmC;LiWcKUO))R4UDr;0%y<&^_e()0ZRyP-s0u($kvu-43rZd>rWGg90-q2jV zR;5`L(naaHep4Chzh0cxCD5A$t!Xkd21sNi6B?7Z(2jpP7dm@g4&4S$@ks1chC|6z z8$uj*{{TGGO2F*NZDO}A6#oEdk;;N(0eC~$5_;#FijG!l!bubBTE?NLUw@*yh^C2M z9gyzF=j)p2qfQPV3N5>AXD5p_RasT%FXl(I9)(Up-S5)0bfa|Auqv$&w($+*mX`}X z!<>BM2Lzmq0l^-pr)sW6dx=z*#BbtxT}Wu8W_@QY)NFf&~ASy6&9L5ggf(YCJ9gR7=^fHpP zxmrChSk4o4F)ML5({&6BAE~sB<(=N5&_9A&rBL4 zLP@CE=y{ll^YbqKPJd1Cc8jHWyZbj(GU~G2vWUgI5|Ec9e(;Q8#tHPtIIm^YsG^)& zvK=_y4Q+4G=-^k;7r_@m^%iel29HUk?p6jXN z`lgF*9=|b?>F;ezkcQm(hXC{>bQRSNmCjqeiQtUrk_fVHbotK;yu4@VD|pH~HcPu2 zj-DU9f!gd(X^9pQyA2aE;F1Si0&AM32+HX*t~i_PC8@B}z?wbK5-Z)yBx@35?xN+H zy@?@L*R68H4=7aTdm`#qjNxUu$64DXmv)BeF%q%gyT72KtlER*FpO2z&DhM6?`ufB z$w?5zi|Nzw$6Dg#ZK%IfWbSGBlTVfnOHQ$~)1{KnPPG7R8Q&u_IVYd?f%@jQg*i?Q zuAx#%PtdjD>nn)Pv8qXK=`>niV^wrj~*y)UIL>h8HP-9EXsS4l}eb zf8wkv$#cd%5w$qD)0DchD}RUnDbsBBPo!Q?AtdH8=Bf1PO>`=-Yg3)xSsBhj?ho>z#Ym+zwlch1eKguAj^L|2l7b57uYOGq3OgZ6N?RNoN9opr zM~K}>EO4F3=9SRw%a*qC-p>?3pDo{MZ%^}zQBl3RhNX>5K$6vq#Qtn#@z{*|`qlEZ zBp~qYjRMHGDj1C5p#XbO(*l|yh?Qb8IO3s=XbnHjw?C1h$YVv7#D{ct^03ZF$WQA} zn!@E`J&nSWWLV?>0Irg#{WC`}`B`S&Aw!9;Ayor7k1S8O0<)Fg$Y~|J8#LC^;53sq z0mzOpp#El@V$eHvDw!=*jh3<>P4j<11zNvwRuyi_{mW~3{{Y|xzw{z#aY26|I*r~4 z6SA+OVLz5AQIBDBG7~J1%#K+90JL-b>b(WK4cQsnfGG_j(e7m+1C!Q=VG(eogP(s| zFLW|ww=yF#5JKZQIL3XdT+$terwkCN+{Xa708s6@C8&h)+?0+bmPE=Oqj2J)P7XJ2 z#Ve+D*5Y8c&^QPJ`HWOxZpJ_MY*(2sOG-;qrZamohNrDHsPe639&j68ar`H^QS0wp z;joHc{Pyf>rP$@6y?-jimrf_e>+K$B?g=}kQdsT8MTMNkG!LuT-bNKiA{fJ)=G5{Df}B9-+tce~0Vwd%D+b?eBuuSzO8)!(e@CU6+X3(Z9N6=%F??Pjh zKApjS>W<~QQ1=LImzixxJm6_)Q>TLG0*8-v&-Mum)=*jY3hxLWP{Ll!a{_m$SA#kiMEx`if25+sW;bR7vG zsr(Id#wvdN8B5)In!X`svhnt}1ZF3JujQIDa8w67#t8OdRfwg`uGXUFUB|%Xu3AYX z!Z|IC>$LO)kU7n8PMob|zXnokxl>fsE+fB+D8~zsuE&`WX};+R9@5$DeJfQM#db4oy~*zEbsGz`lH*TncVsMrSi#DU3HR?wN-j1| z<#M}dD^De*l#w)$yrxh_4hSCD=~AdU&h}{%or|zF?y+MnwZ@%2)N%pk&9eqG{p|PP zel+8QoGK~O)AcKvKZtIZ;Cr)f^4)G+{m9Zmlh^NYitkFKot!1>Z3t=!T}c^I_)}hr zX5Xbgvn@T9%&B7@ zpAxg&4a$fKZP_SSr#z0;i{*>FinHiiw~q5uw%ZJG7%Z8KxL`0k1L`x+vErrCRQ>Bc zO{mg!TTtD1b}Rjf;vz(m9}AWaJ#&GYY296-Y}Uq(mwJ}Ll4c{S6OG+_VzQ}0$)<`@ zwyfzitvg24uPv;N=h&&{&6*DsR#H`}-k>7L;213h~h z)nRH{{NH%hYkXhuPI?Un)io(BpIVMbjf%7d-DKmaAgRH}vnhO z>~}4wVq*i;kSlIS)Vmd;k4qB;dyC&N^w|=QKNh2tO~TrpC7*}%YrzHFH&@WdD}+Xn zBC0Vs<%z)OyyE#60x1X zhDA$_MXy0FPUQ=o4m~>Eu98N%gq?ypAlO**fO)BuTXrV!ZP}IYEcGipgK2kU#HxoF zL(3H%c*s7-r4Wjm=8eg{h`dX0DYmtqMJcfYY&rQs?lah$-i#a-nN9O6oVyNdVoTa& z8_6$%j&=dxJ-?kR^b2(}a7PEI;L`&3opU9*42WB7f%3V?O4l>|G2$(ALDudyKev64QMgh2 z%Y@3zGs$ku4myvmDDp{KEV-%kb4edXY5xG)o+$97SC;zayW8p(rz+N9fw*CpA-f!m z`&JD*YxxQ@j{K%D@s5|OX&PPfU%~yQrQJsEo?s?q$%;~+-US((9dZX9Dml}%x}#rX zICDj>=^r%5q1?1=?#iSLlpMF^U9L#yO7}fez?yC8)2zRi0)5-x*|l`MN2S*LCw8{%cx$3v~}^eGg92&)YPJFRx%tJiH!!$jk_Z zah^^GMdbcf%I5E6RG}1k`S`(RZk{_`3TK0H5y>Uep>R9nZy%j(;bnLE3JNmV={zIi z%|FIkyn1EEmiEF`c8r^lM2wiq5}*)pLGQ+EDeIztp|hvU9&~Y+9}%u+)g({&L^Mbx zwk9`eE8 z@8=%$lrXV}KTlIfQncmEVowzKDsKwv8eX?GwXMahGliDZPX%(rmB4aw$r(B5DRV-k z9AlxKIZ8ECeRerN8hCG1(r#ncHHWo-?I~TtK*icTVBqjL0D+Om6!5r;b6k<=6=_XN zUZgj&9T&pqL`a9)btxgfncE5yAg5vx+~nsz)KZeGQ=M$IHpEeMV5z-c<87m!(@A?Y zh%C3WruPGdRRn->+nkf_N3?UBYgCRZ6+NY2WujeMIxTW|H2XVwG@JW-)Y~ zDOd$4G1Wjpf_ol%)zbD=9jBz9L!%KwwLP_p>R>m-tzCSycr7E1A9i7ll;;3rAo>r~ z`qt9J)4x^oIg`7&4gUa$=D5DNmMeJn%KMZM%v4}?#}#Yc_e#;Ll$_k^7!yQUMq6k? zpni1auFRB)^t~$H=0yywu2)8+2LUF?qCejZBm z1eiUbSiI>ax!MRojwO^}sSHm)LTYMLT%en|nh}nREtiq@*d(&d6aEM9a)IT0-|!{ zS7PQ@HM~vm?^y7Z5Zr350k)Nj39YwEir?L1fr3XP)P6OMEJSKMZR`I40C!f<=GGf8 z;=OV7gFpKe+1%(Ug$clJz&{axLRpZ4&p{{W$4mnm0$Q1sF7 zoBdpIN_`@@jnCv5(r}S#cIY-u8&gMTlTN!(dC!vA{P9Vqm4fkea%`^X(H*X(ThWFp z4usW!0oIdcMRgtz%m~Io??}--Zi(&2$qDQ}@35$-?v9)!c_n|iv%D7M);7@Z=S}KK z=HLt-l(|iPt+pgt74WBw^!n7ewW+jf#&(M%XDZQ>IABa}2H%#YsZ7Av8lcd0* z$4^Sl$5XZyla7S3-0dS6?hoQIQs8QIlsyij3PX zd5n&Cj5GY)0y1%fU3gh13oeI|nM0PMpK;z|l^qXWy-Dp|Ngi#-#L5v>!2=yftw_@? zTdK=CtA%CAETm)+NuyY5%smp;3GJma#xU-~1auuoUc4HqyX1YlK|t$>OKAs93OgnkqWhA0KFZOtNs98)DA$K^mV2=$;WE+`9XEN<~gB#oIQ z9T`pt{6z`dVK}F`wQ1tLPTvbNrP>bD51an_9R7K%YGLV5;*Z^-DlIJ;yKCaTHsbD%y$v_meM52{pgr5A6#_&Ym#)TQ~1@2Qc~HH zDPRH4F;cssY|cvd0i^5ZP8QDdBX?zE&JW}I8vBfk8mAQrJywT{j-$%%mWNHEYb{}_ z*-rOTMI0@M+Dk6e!OC=DgP*N?Ifh>hT{ow^w*LTvaZ#%2%gE*4Nu<_|t?b`!aV`TZ zlOrZ`#_r?{;q2eM*|6F!pK;+iuGV#iIN2J~=5na>Gctn{C_R;n z*WL-DR<{AChB=!pJ-N=-+yEQ8`W&gNa+6kz zFoO4+lDa88lix=ckz*yf`#}hbvL0pNMitN9B>e{@bgen0z2QnR<>kJmbM||!F7roL zwz|KYbciL4$1S6y<&-cOa>I8WtD1G^2`ZAhUQhEo@c1Q&P88bawe=}l_&-v;(^h+1 z2^vLVmEH3)k)6R$@cY+fYDw#T4>D0pPNm%f;@x!{eI{#&Vv!_c6rnh0$im1^RpW!y zWRCUEUNV}wy*CKEG)t+)e5mj|F9+{QXuunhm;;bU@Z=IP>sdl{-m%njgy8+-JEN#^R-cDHsdJ)O&~yEIR= zvfT*JLNmsAu3C_ii*((M=}r-LpHnhRYq)M?M1pY}xkAjCakSu!c0AV5sS91bOlroa z{{Uy9xjZ{Abk=+2G9yUdY>9(yv~C-K&jg;h#(x^#Z8+Jz%%cX~>{`@(L~eDPJBzos zl*=458%DPZVIZq01bn9|dXdi{ipDhg7rKokqTZC?t!i_BYs~UrJ@V&aolg>?4WWm&2Lh@O-Mdy4<7ry=H}$; zjc(VFy~(|r;Ui|l#U29f1JIsG^#-wo8mi`p7)O=1wPy&oP$Xp#Ic)vl1`p?542bEV zHPj`QjBX0@GDSTHc3y80t7#EwrUvq2n98l6m@)VCtffXWmp4;NbzBoiQGas<{{V(` z%~D+^-VZ29WxJ0m*9*G~k^ST#jOU)5R}|-Is!40y?4=dR>R#*eDq4B)=wa0(7Vn7> z+;Ypfjl<}22sK|=bmz*|*)I59wCUkX%UM+IWkn_@>a3@z_Zh`&dX-|O z9n2*fbe*2V<+y@fPgb?DmCdXtY!gbL7|$PkbKfJaZt68FGcRKvTbH~x)8FY&1mEDv^~rDa3S@7Of?y?+*AgRn)Am@2~Y+jXJ_PLK#45oe3Ngp@RiI0X^$~ zYSO<$8N%}AiBnh7Zd=2bDu=>s%*DH(QQe392z_c z9Emg~%MX>*@}{?F=T=*mBbq<3#S*MQsU^zibDwjXl%F-&?zsxLDG)2M56lJ%pU$Wn zNf#_ErkhW>F|PEDlJcefBQ}%uFqJ%vpW!Q!k6PkbW~=)}^Ex zigs9#9RC0YcAmN9cdt!h;V9kidFrc+DELQ88okBli*{68^(8T80o+u zpF_rLif}rWM}4nz)pez^)pXgBAbDq0J7w|?Lw^j9BM@0OGyOJb9(fE~kMgx=Fb`S;KgD#n&1p zlO$SwjFD?sQxQc6Rrk$sJU>#qWsxJ9nq*H!{T?+bm^m zPyiIVm**aX82VHy^L4ptYL}6%Y%Ms^PD;}CxnD{6BWtQP#P@ov_G)Esv%rcSl05vI ziRyOaIK_5Kysu<={?C;r^(ty!AGz?Sjf2p#qoJji~>gq7}7c%|wQ4SY(BjzIlxtpn0DmS}- za`iNg7*lPuk*jIqt!r9KDJ(R*ix}>1L~VO>BD+kh$x-}G^%zmwlfy=UlOV zw$Q10rfNPd*0pP=w_#?Z%CiZ5P<14qpvffGF?N(`)7C}%K1ey$(HQ!+lj5yk zQ?-u!OuzdTq<&4ii*+mzbPPu)pIqX-N)#gn%O=k*wlCUA$~L)TZ8rKi- z{OF7ljguVbDLcN0FRIvDY4iP_8?UrUkDTlPFSlN6DaOvl!SgHFb+n>;n_|igPyn&Q zc~RWw>UlMdMRj9pPnKNEb4puCr(2hv7+&Jxf%`!JAEWqg&9vYly|}l#oMn*_Syj6Zc^rGtsHGdZS@s;H+`ZMI z%)zG{oo)*&E0`xT?S>~)knGKX81^|Lxa#vH^tsnUF_e;S$ESQ!@c#gaw3{t8BZ-ql zioy#S-ril}Mp8~4NCXV@?bE*%!$uy)UjFRftwmPqJ6`9Q&7?5eVz`gXRqHcjpG@?{ zdTyMWIb%CClGNxOb$=S_wi-R%)x4KBQM4AY+D3vRppDpIa7hC>I2?P|Bw~{N)O9+l zsdG13{-y=anrtVM-u~9^31X3>lX3t91IYJ3O3}_e^*N%HpSnqXqzgMZZW1}3IaIJ_ z90C5&?d@1n#73l?Q`H?6u=1jleMxjXRJyrEV;m9ZYs};2jZXxg+zjJBl&ja3C^r>% z*t{(WR-LBoj(+xAQF$WRw6A885erTR_Flx}y>(Jj@VP?gZmQNE2u9SXTJ;k*+(P~ZoK^kWiDQI zuA@Qc9rHNr$(TYz!COdplbRABcxt{F%7ws+TUP2(PD-6Mp*ySKQ8);O)9wuamt zuDNL>&N`FS3~|$`u9(K!l;almGhS6wnA4p7=H&kXg*&3v$fS|wAiJ~UrsJGY-*Kp; z*M1XtL;Gh>lIq)1TYH%2j$&e9%%w>xti3?*&wr(JVrotb&{w%Dl|Fgwi{BgkWp6FC zeiF2QHdv<<-pDb9ARVYZPFp9YFbAz;hJ)%_r)y8S;)(!W#|O|F>UVC&(Md;{jpdKl zsk<4ab}1O7nQd4UP{Js9Tqf^Rfl_FdmB%c6`*f*tG>tt+h1zOvYNKLRUODSh*|cNa zkw8>wc7hlA)V=I$8z{N<5jVpB03?MRF>Oi$ytG`*uA)#K9RmhK5yZ_=!r*Ke4a{GT7C zCsI98QNqdT85aDlIimzyPahxh&TvQMS3j+Dx{1j3;e(WErlc_0+Az#_GY|LSAIvxO zqn;xAwPvoY#5MzvE@5HV{K|io9Lu90u&Zx+Ai7=ABg^vKA4!ux&YjaI+0Gi-*lot2 zs76c4XK<=Ni_174k4h5IiO%x5StJ1PMtWkCiEyf~qb3iLkTFwZp-%dXyoy#IJ42FF zw4C)coJ)mQX8V?d#za>kS$-HP+*hkFQ)2pPeuRa)H2SXAG-XH923AL|hQ z6|8C|bIye0sb3%cdb1`V8O01r#L5YqHr^`KV#3^ccWM6sO-393`XARdjObIJOL`F0 zqV#5@ej(B?;`=@1EdsaR5{>Ekjz{HLM-Nh*x#>$^xS2I2X)OwOdUeL0hVH_DwP}Oz z6=42f)Q;8knRab9)hhZ+^E}K(6?{a&GrR@cDsaiP@q_K}+P$1c28JzO zOJ3&$YR;3lLZ67*`a4e~0Bj8AQ0h2Rc)|2F^EjNgrW;AC$GPao7^>TsMri38rKgBt zy13VDVZFJ4#su<6@qr)R+Dec(Bp+X!(!9#?jVSxNdK$&Ll5&!{lOBf*`m4JqUQTLZTbeiQ5{gekozL3h5zjgpBar!#FE~@4ah~}V)rOrZrx=d6 zw9(0$)81S)rLL$-*Wq_DP;?x9Y9US()u613omtP~Wq9shFc`}0-9QdN z6=WK)c%N5^GZn%?65t)NhTKO_y_27MT&eUO*(mXDt9Nqq-O94$eD2W(W+#E3gEfTk z^qbh)6{eBsUK;QV_@hp|)LUG&j_TsdMTl%0M>}xj9tTm=JcG_Fo~+uap|y?azjpS$ z&U;1B;}4ouaY|gWvpU}#Y4CUlT8C8d?y#1Z zEESsCN8KjLI!J)$dSzKoc^DmYT-4TJ@?kG~t+b3cLGvFd&f@k~0Wk0-ZS z%3Erhb2Y`Y9v*ccIRJ6U1dQ{^u4<5!BBHfMt;SZDQ-QtJF6{0X`#SzuT4XS*ZYqx% zA25&}qmKQ@6{HtEHFaQ{P?PL3Zxrdq(r721=*_e4g=SLN$j_%W3X{FeIqc6qnMh&^ z6+hiQMRgdzB1r9~j!;}E7$X~n0@RGz++>vl1om-7&}>NzSMauBW?p&QCkC38fPLrM z30Vq|>T}0qOGZ1ju$Ja_S>l;ktmAPcavM1WQF|)}?^Cew_0(53R@PHWmh(mXvSr~& z?0%Ki2}Q%q#l@yNjspiSh;Fo}l~JlZAC0A2RoQ+x0!{eKd%GNhn zp$n6|ft-{5=H${0{xFhq-mi9LYs5FUMT z>t58LqPrd}TWfQ=(C^S$-P}8;$c9%Tfd~dkACLmMB`tI{aCTQYUyI%-(5t6PYL z)~uMgiVXabqYkoqgMo$bj@7(rdsMDt3CfJ*;?D)tCA+=3d&vPlYnIpt3%HYjJ6F9& zFI1qV%X5aNEzVYQI}HoOl4yEVU_m^Zs)LDc9F>S4yR>Jp#~cdqa~fa2Yi}fdc2z;v zprt$8^Zx*VbMs2h&pWwiVoNbRl20bEis*YYYB9Rl^sfc@r%#s6pw(_x8RB2v?l=wr z_4W4eT$L#`E@W|HD7#)3v)}scdN!TnEe#e(S}}DrTT93!k7gV0^2`TreJi$wXUS=| zbLX*mN%Qk0l7H7@9{1xeho@ahYYvHdBgV{xr<4$tI6GJ|1aZL?*%cWa4xcXX@F&qe zBI#Z#w5 zbHD`PXTR3FXw`Cxxy?8vTl;7u4CMVyZ_Hz0d&Lnx9A_BoO6a7FbeNq==2E3paHWS( zIOFoAINCQ)QO8%QgC^#cn1?wz$sKD0T?=+sHulV|D>odF25UJ*vg2e;HQ$}6s}^G# zfJPVb#a%i){^*@vOSW}-4y@Wm`*u!--%rZiv2aG9c7?2ft=Zunl1#){@d#7`0Vd^H2Civ>eek4o*0zqpDlm^Zi zujp})(uDPP2*slUKNR0Xqpq83VU4b#3pK{lW!OOl2Vf6hUTb$Nft_l`-PI#(6v5oQ z5PKSwhMP|4_l*z5kK=EK-Y3(wX%Jr9**wNg*bbp`tOslq8TUEwT$qUH?7aT#9TBtT zn)EzEbZH?1Nm!{kjRNPI-Zzv}Y|c-Ww2p^F@e%ON{8#bBlT6kT$8RjhYR04Q9iy&D zBDJAT5ptJODpj17na*l@z3!=fCZlsJw35uzJ_!m5>w%stl2B5=nRKVgB+0csR!iuk zDoBM8DFEYl*Vpl^C}EW=_h&{Y66;Amh^OaCI{kv)Hg}h3DhVY06m_laqwh$?Hz&&! zUedHJe^#@WO*c(5+sBYVz|K109<_~JWg2qjlho*^gR3}mrJ+S{g`R;ey}5L`GkoCi z$?N>FUGS*6G-oA9xXRC=R?g~wBg*rn^4a$lE=bQpaq4mUR9Rl_5h=>nzJ`sqrzN~@ zAeLC}vz*`+IO&|$@T%h?O8)>Wk+64>2KD3VDdww2I^!tYhc9{A#}QAu61f>@b3 zT00&80ETppKFZ$G>d<*J%WoU4#7xTVl?8zygV)};}P}xc;F&N~~ zg(r4650qo6?Md!ZT4*@3nN~1PBOLvE(4%#BXIi?9)ML<c@>+DosPOyl;Y%t zVhRF1YEsnNZ8+Tz4f5pU9WzSCs#;x>OdV{W3U+YE&~uOHQw4ckyhG)p(5R+sneFrQ zNDD6?#0E#_)A6d=CPn$DD0P3DD;L~>`Vwi{&cr$MMND>nRXI2`tY0>XlgE%t2L2L9 zPSp(?)00iJQ{_-f5>z5F_sGHhYnAKxp4?w5zmk7`<#5<18~}K$v4WaULEdH$KhBpa ze$!n?r_2|Rymo`0^qz*0YDsy!76i=&q*pML8!U|rvbGLSDtP=w3ioRMWX^F)GPhQr z=1etfrvYY>Cj+&_YcJ}+nrclF=+c|j2cKgpCT%VexP9w+9x@N8rZs-LUN3v zf?Iw#Fa}q7i(4#*nB8v@3k~^so$s#WiCz-VOBhr?IDQL)TB~-SBS8ysZ$jBp{ z{-o1rh)E(}wQX~^J$U2{Qn7r(dHdKbn{YwvLr8b-Q=3hOVnjy_d*IVjaWS13%*dA7 zNC_-B_4Tb|G|nkelzj;I40WqBdI_IOW4IjSr35wuo+v1Hd}4u#*R22`8777#1!yMB zOi&ikOnqsP!yc5$XqwLEdnOQnIB^2G6+34D?n{wjX{xe|G9JS4) zLE|T}X!@_d!;kiB(!*xeC?uq=jB8~oqrEq&v2mr`+1rb?cy6qm?H5+f=0Z>RN$L;# zECKefprMV!;^WI7xt=9_EnGaUHDdLYHnZCtTA(0qlVWj<<0l_cUmcxge^r;Yr2WU! zxBLT&r#x1Ti#XFQ(6!LEi3cU010(6w*NF(!e+i6S-q$cSe->%G7T&_yE)p@d1knQ? zJ%bF7!xhm_4>hEj*-2Gj6o_N-<0kVii>DIG6<7u=nCuBBk7~}gUaC8}%kMf=o6t+I z6wj>6u~})hv9@<@hTC(VFnTAumNYy~<6jZJiE&^pwDWBy(IvzY6_a4(30D9vDpKSY9HEDCrU1)SssmU9sW?s~;(?OB#wJ6#tlt&_} ze8yFg1F0U^I6mOlr$V${`y6#3?Q@**PlzGcpG~`q?Q3&yZ}xXHIAtiTOnWluR3ruY^ z+nKI3*nn6bc)~eeryvuKN$*ml)J@r07{K#XNn#QY1eyTe@WV#>r`TtZh^LLbv(FL% zk&knNIpdnntx3~^lucH5aA7L3z9t)0*BrjzE9BJr!Dl^o!)akz|d0Xgf=oIK9Sv=U}d;si?k(^gVb~BQ9)Q%ShAQ{(koO;%BR%ORkW=Kl*Do<`IN=Rv7 zS36fCm~xIgSaGwTLq?&aTMAGz=V(0hgPIhYCzTpjO{f0=XWR>mH8Ws&Yz_!Lk6*5T z3eKmry5(k+;|qJpsTQEoNpo!qc}YBbC0*GLxebG!ynufS)0NuyA<8$g=zbx*)NY`& z(sd?|-sIR$+wR;(vLdJ=RN2AZv6WZ92E5uBMM}J?$>{d~03*|@iT3ndDJ^=d2Zp>u zd^{^}qTJlcZ=_qq^UM);nFlSmJx@G(WrcUsqTJe^&!_pE)5JkRPMY_Uwf_Lf$+y)n zHSK2B+?80_zjq94g^YJT=N-*-*TKr9IV9|iF!-fTeD=HUdq0PDdGxp=h89U;k+GQv zEI{=iO2&o`7=P80eBb+O;07o0fLLm?#{6b@a60 ztz*imEpA77D$9E&)0|;R^&nvX04n%g-f*5SQijLr*(4_n6d^tAYxrZs0s-Yo}EdGD?up4T2)&#^qm1aDkpCj&Ua z^!2KYs=Bg|F5ah+I`Et#-0qpNVd4J(hW5G=PjjkV+v>7MzBVr8BXk6X7$Z5(J8@lb zrBZNw`@gvJt5mwK`QGw6ZwrPue!* zN1^L}3GnZUTTxjrbvQhRjbgfmR}voJE_lbjYfoj&z4;?GE>!G!wD$(vP?k$c^M^Yn z3xY?`^sN%**w1o7uWEpQ$JrzO)j!g&o<#DacTThMCaq$(5xhd(&jv{T_77eZ@tWkT zkA*pMz0D&+QddZsX$NH&!lOPrk?Y=M6$5LE`IJw z=m#9qc&c>0$vs5r^J|_eULhb+&#z4XJ;+#ZUTH-jzjE<9ataH}K93loF@mLX0ToN#&{YReZmDo>kc zcHm)C+g?2nQqeE1w2uhA_yJN`Ttu8@Wd|V#r$dw572ryos#I}D(9W!BQ&OKzU(E6g z*o4-K=Yt_GPf~J8>)yVi5#@?U$<(OoP0P7hK(Hz(xMD!1;(W2W%9mg0M_JD6>(WCZ}=d4wna&-zhS`==Yx z=yVKX z*P-T0?r!C&%HROm!1=i7eJdWPqe*hpu)zNSE)Pr&X*H>`o|a^><&>szlYo8uRKDiW zcZ-888JXZ{f{tU_%08g~05MXPoy}Y4gO6JgLc`6snb?4`C}tf%^{3q#Cw@wgqq8HL zGZ2v&;gV9UJuy_bBBJGsUSh;c$F*`;atE#{HD+Bob3)5~$<&5`Azzz$!S=x7p{qNc z5Nc87dza#n8wrDYqUY0-Tn1y75)bHTO43MGCY?D+F1P;xG8A!~XX#C% zVKsXeGInCK>UG`n2oHK>rneU1N7)ut`_aq;eHed;Kb;Wxn(m0i=?KYme}f2aCAN^u z438TC7&0+ZJ9QNG=qA;j)z*yB{@uP}zi4>w$NfFRvY)2r1-~Oo{lw@~>8GHLb);Oc z`c{wSzH(m5<)bI?edE`)X$p=z9M$kjro7tvf5Q!ZH%OUSL95MglaRNTq_6$=87Kb$ z9Iazs`6{T3lMO?Qs()^A$>I^)piJMy?3pM*jozm= zFB0iO(cPIA*Akfz&nEIPFx9Ji9z1GF_r9j}ySrPbn!%iX;yBG=C9ThA30ck_^VE~f zZ&OO=S1_wC-bmzn8bv8+HbxQyxRajkMZvVunQ?AZ?oe@B#%CQ`R@D`E5!SBfKEmhU zpITzL;P@O4X(F#+-P^4`fpOcgI&x@3Z3wN&G+aXO6vi8c1h?Em!kI0hSDtzd@@NHd z@&ybw`cqzFl2_2Pr_HkIGDuwG6*JiA!%e9jzLhoBpKcQRH071IcBbOw)bgc5_pG7wey4yQtM7!;0IE?XE{P}MtKEs=ugnl#VmCf zLa)TX6XSC{z2T*J{3n@eN-yuHyE{@@T(c+`RV4HSupX7cJQ|9%Jv@&#GP1Kp2Dh0x z*`Iahxu;4|_>p%^ujU7op^;Dq+F16hP>f@FE1i@u>Uy?sX%I_d!g!^1E;35F0f%C7 zSk!N-bYyvh_zyi8(&>X3g-&#ybr;$Fqwu>OZx2p?6jS*WHQy37 zgs_X-TRYJ@5~u9z8!k8)*|*>F>0S7Ib|yPboEW?o5(!=#_AxD!!*|k4cXwqlg-G6( z^7(8sagqo=y{hYDXH!W<9MteMCb^*|t@9{fX%I)NUE4(F(@aG&hbPQuTz~;RPbVF| zmBgg&Ww@$fxg_<_=WgOiZzM>`Fy|=Ua=x_A1|q9nPLSK7^i}ouG&Tjhcx~Z=SGYp* zvD*$C3b;JCzqjW@1VwohTEiJ$1;E|AyA1yTE-1L|)YJe%eYilZLv&m&6 zP34cYD9QqT`QyDzn^v$wJkHk*YgN?s$zizE9Nau`M{g8?0KzVIrvsD8j6o;NH43vlUTIDVbc#|I-l2^`jw zy{?E>*^}$jCDqeU3<(S@?CPpM?&3}f?~b2LRuEQtn$F2t6>a4v$>74_U(5$@&C?xy zDw@{jX52ch)X>3eD+m`;Oj&_P@;RS-t51S)-P^tOlZb?4grn=m0Vw-G;Ttd=_p@(ED z4&LnEuN1q(_Xa7Uuk*$DL=9@ZUGM%1QRV~*u*1QJaBq6aKJfCK7l#;pfVx{PI` zcd_&oswv@l+;z78hcB)8gk5Sz%E}#@dPlN1VTN!HOA*^Aw_%F+FzH5qXMs`P7wBmC zD3~=lbpi7WbSWa^Cu@SczXOWvsVnGZO=)wuZBk7ct>ltD+HjR5ONC*y7Rr_VLHz5J z5m(vMb4vC*Fx35}SksfW&iBKY@fqfi3}m??LhFnb0~q~l=c!_)R!z@#dRWXVq?azs z^Dp?f#TK3y@cU{vHr{MOfMndOoaY^SoPogSsqAaIoPDEEOICRjR=blvQngub=a%kq z<~)p9nEwD16Ow;A`z*ePEOtDsMNWyEQp@%YI!TB6!Zx5E-aS7qO?TsKtUel3>Suza z>tbURu4P+%v5fUSYxBB}?EOm%?!jF2--VCm&8f&bm5p=h%OB}om<91O#^PCfwAZMg z5j9V?8@p8sW3+{a0*tej%V+c-g?cYYwtRzr@;u(=+WON-x74pKqg!HvC{{_+eg~pw zb~^U2IxR`rGrCGkbZK}`$68;92BUc$ zj#l<_dhK!dXO-GXPB_Uq;}xn(IvCuk_HF`}&wiBofKRe}nmK^G9nJhmsU7{qYNHCP zBLD}cGf?9Pt3`9Yt|V_d{DygavPopza8J0cX{6+da(1z9&v1%o#z{MVf{K#9gN}%~ zYKQE*gQ(m~7H)#GzNNKfW1FkUVAw9@Si2x&x1VZiF>dzMi}WLiBv6^;1%PmhJx^2m ze>&epPNdP@PGj0x{E)??iR5Cdk%B9Mw2!+!laL_YN2mL(cSgWS_KbR0p)YwJISs*SVv<=- zB$bq7IolL3xx6W)K++d-%yLgt zP}#Z@G_F>HQPf%!#XP2X@Byy=Gt`wksP5&Kljoa&Dv33W1n+=`&Wm<_Z}s>xRD2% zi*szOd6lG(3b^|Im7gPO!>1P}v^+R)pjPLwv{n~1hj7gSa`u^~-s+PGEaCGRj^0QE zKT}69Q98;Iw^P5;J{xLq80NUR)8XIFTQV@mpvFI1%5@_xIa~RdN1Z#SS=K%cY6>Ku zM${m_X8Db@qsf#00!b=)J;>@SFKs39Ej+b4p;Ni$mv)yrb=>w>kh~WWF-aqFf~0me zx@t=3wJj_*IX`nE5%+*2*pKpPYg0%{@pt~L4(yjK;OBQB`h!Z>Rx8>{6&$;Oa7hR5 zw^~*yO*ZedB$hzv!yJ$|A5u66@}X{2^^0HDok*iN@5t#*nbUSA4T7Y7-oK4!W2s8b zHL-pPK!#LqRq)5^KmB!tmZ_el3CEI3TRnc_5OD2RZ448%nB;HXjPxt|(rZHDKYw1t z$o^UZo)1rIhOW+-Pm(qzw`CJ-lw}(`#(N&MGFn|4QfryQTR+sT7$V@r-+4(H^{plF z^COm()n8{V(P5ivMyIYbS*3Q_*C}$wF?)(z<sRRAaE3Nn zbtiErl&q+ukh~gjj+YgBwy7AdfxjP5N-QpWdKxxb?x|v)U9O>X6o1@VPSf>XhKATW zbf@vfGIrNI{oifVG`q5YiS1T4{{X(D+J9OUn%Kfv8V_r)pZS>{BcDgOvxYluE$wd1 zfQDPPbPL>VU{9yVhZ`PxKJI41o~uFn2NH6 zHzavDS~00UR(3j{hu|~9G}~(%JVZA{_sR@}midD@0QAQP6~TtYN_XdNp4_mCHgJdB zQJcfM)I)P=cT9(X@TZ!m@l1;A_K`Dbt<1@lpl>c^XrH)|N}txLNj;ccMLGBP6xm6z zb>0ZYG$&`dnLGhWBCawyszq*9-OXrobqvrp#SM;VA27Sg{^&xHau}^P(*-9?y>G7ED}cDcAO-kU;7V zM^TUu;aNg+r%_HjqtU5K73nz7sLv4}?JX!@*;|k8I0B227$3b{e9~Zb868KjHT23< zu{62q{9RG;b?|b-)a9wSbBl*pkr;WP1O4DDj!e!}Q`oHVO5tQxW=W*R>~&+>xG^*L zw>>O9Hqutzo%W$5I#tAq(7LQ}qeHy>q#y%t^ZMqz_^YGo=Osr+wTN`xQr7DC3m`B) zc9{+`Gmt&HRmnB6G;79g>thGSajbg7n30wT8`N$EcYhYrJ?nZeO;0m2hb>F3nT)Z> za?I-z@JV*Z>D1M>d5PO%?Y^lUyzKMOJ>tlrC660cMfAzZA6!&6nY?ilPVQQL=5^)8 zyRi^kG;7FTG0U(YpwHn>{mxpPjr*lVl2r^4vW6Hq<06roEL+C&u|{@O$IQeN$DpL- zkcGMzcTOC`DULM}rbciFK}(uF$8jx<#ihhk2(aP7Q6l6q&q5DTRTh|;&}#P=4{c{0 z(5x}r`O(G(0XdMi2p)xwJ*xHb#dmIAzenGWXp)#95?dppZU;yf&o z{HJgj1&IfaIRo>qbw(Wg>FRWSW%BYhp`8Om8FiO2;2{7-yDkr=MgbM9;@aHKGe|Y7 zn|qB`Yl}r97j4Oygn#(x;8wJgRP4#CD9TreTAORgnk&1Xvt>9UBKx`Y9Whu`=Z>*g z3*4t-o<^c0l0zJAl@f@_^OUI5iKf94(-MjG56^!}$Q%h3X zzJ`g^Vw%Y5(n}zQDGaerBFQX31Z~e5!0+0wq@^e)ZGA(Rp-F@;0-L#AM?lbUKk@3$ z^*^R11`3vuyKR;5%0XcX2mb&rL>o$ERIe$}et26I`JoZa!nP z9*TM7`U>-K_=rxsl=^Mtdl;-eT6lQE8$H#|QtwODH0!p8^4Zcz$qaa5)6^RFsHjTQ zPUnXvK7EFvy^mA1+Fr!vpZH zTD4-+ie=xGr`X}N4~Q0C9nmGazK!I$jaeN4hw-0rQII@Hp8 zo^7i5qge57r{`+7^3A==EJeR}7hn;34lCTkQ;al6ky>q|M$XlIwF*k$fI%F774#IV za`Zf@>dhwbwymr+tQL{NP?kHIP@uMb`RFU>v#fflQBq%L^FE&@r3^hEZ8hFa^Y1yM6y-R;2<2_phmM1+) zuG}otDN09)m{h;UBsO0$Wp&{{TJKr|VGc{@~?fkB2n?0TIGU=mhG2 ztsh&Z=l2II2JqIT4h#{NBY-9uG`_b>`at5q_+wU5cG`C2hXhr}8%1oxlr60;RyDOn z%*BZ+NErH>)zylRJJ@|RCZ5{v{2O~{w@HtgR{#%Mnbm|>JhUF`Lk{}N;?8x3c-bW* zAv=(IRG2Dw?QpV|ZQ@2DLF>uMtft!5=9Jaumzl%(!tJB+ zBihCz+h=VJp~q%K8^2OV;aqqG<4W(Yr+#NqPPLaqnOH$`$)VH`+^dyvK|QiLp{)dpZpK@tB;zc9Dsj;R)teiZYGrPS+ zVxX*zBMmIbuddp7Ce#@kJDJIRN5pH+2n)yqupO#fcd})A7ZRiljLJi6P z0K<***C4UvoRjZQld&IITeq&`%{3!^&>R%C=`tl2eg8>B)AiQknB;y}!)`*i-yeCqXrHDXD z%Eme>_Q$nms5mZL9`!tZ86@KLDz2a)#IZomWa6V0fjO}prA=O@r1qw5s^0GSof6}uGaT7w)dl)p+ICX)L^IK|FTVQlVD0`SY6a12JL+rjl;cr0DI73R0On>dW%9-&QItp(JI zn5Qm-653eE`5T^utq!zbQX`3R*%E_rxdu`_wwiv`bS>kiA7-80OonW)LV%2Y4{CA6 zI&SfHey2T3(y0C7OT#Vo;JUnyNu=5XWPRWU1!+?qQl_$znzGs@*!63KEB=ljU<#gK zEwB!NdRHX9?Rk!sjI6eH8g&^Zm`ydROCUMi;O8|mh5pkfc&fAbnK{=k-W{GqTQif0 zS3g|iH7c6^Vq=J@9a9N4tDA=sO#}}hP=VSkkOyi#pJ_k5nO2`lu4S;l@}(yrnSy^h*00(9;v=mK-kIiLo* z2f;Wv=}kK;AQDmm3&$04S{khpGFwi;oF)bV1d-B*2Ko^xCP@vA)3`-I3lHP&2c>B5 zE`+IAw+51mT1$x#%n#jSMhCV(tx~3+#141a7h<>5E(XswTXI1K(Rp9-rOOqtk>i-_%jLOwiHqEv3{VTZLP9VgQyg#(tfuWV;51{{Rx`(^y=K zwMP3i_&bk4$LZ_TeQPTXuEr4urgQB`R$^>9!+qtc=yZhLjC;MMMDGvx_9IG6qzmh$dbTZt#zWz4Q`N{bP38-}+aaj_ryfUh+$q7<2LjY=A z+ILJWMA~(%fm!Tro7d#zRVve4Nrx&UT^moxV_^VB9T`s){;^Z}m~x`G_64HMTkM0& z2d?AIYOz%<@e7o&x)qM$`-SJavjh11R6JEqU9cx&$_9JLX*%<^p<*MYKzA`| zBYd1NBag?uSJr1_gO{+8SQwr;BDrz$g#odW0m$k7Y0A8$tt2#>EnZpKUB@wN3wP8d zm1P%yapM5zYN*2V^sA0CYCN{n$Q)zaQa|j(xMX=*rg(uX=C=w@e0~+2F*2uqRJ8{l zthC;FI9Usf@wk6V!s~LAD(e3L==yZm49Z=AUqh3}_}5+s->jlVO%V)haUP4I=-~(# zH-`4;rv1g5#{VPm@SD@L9gg3yiYBq zT54U6R(mU5Qs!%!En#V8c6V)umM16q*Nqz0A);p08l0S(Abl%Ig5GAUL4X+x2zYIG4^=Rn<}nwa(GYk*jKNLuk2~>shneR+9sPG zsp5$v@_*6RNhC3^Wd{I$WKoE$=}_jrqKdmR@1s=KuA_)y7G_b7z#LaaCfzjHx|Z%H zl0+U{WTLJBBdr#(cW+~>@YjN`t>lX1RX^GAA@kY%@EwO`P%=UHuQwB#N0GvdyFENM zUnoo3SJ4{U{E=(=oE}ZP+Spsi5gO!yvT?bFnB1g4bfh^2WZjceT8LrG0 zGNnmAZiLPWqbBk=HzGg-Y>XBvr>|3NrChR-95G#s zfJZrO3c`5&JT#oO^3>WmO0QInweb7S)7##LiU`T!9d{PbL5%TSwX+IoUY4)WT(eHh zXz*^D*#O-S`)D}-0LN>a`$n&1QcRS1A5A$PWzaiv2#_9s@0w3Cs=6=h<~~vzj||vp z3l-vB$e+HBJ=1;F+;NkEpUXAZg2vUWSCyn^QG2?b4wK z3~0(fce#!WHyH1aTzXQc&3l=tTVpH4zAqY{hoU#ym8DC&nOS4CU>AIwhs@3Mbm@VL zAx%Y&Q@*CBhkyy}ue96^rd5&~3~MIk`o=ia4JN#9SzZl;DqES@pb z;)?E53^uPL!4wJ=S9@iTJh41-Nj!C~Y-Lx6ij-BtoUY1^;A=EFYx(x{I7K-9Yk_8S ze`OoEuwMh&yhj()?gOY*!K3?1rT!EvFi!<(>_9q{%D~~3lnnm>g&*2AcjZB-^Wgn8 z7;d__0OSFXf2B|Mg=V%zDOgYVLv;59CEd>Ka7YG^?Ha3ehs-N@GShmjSC<}dAmu>^ z9+cK$Qrg8>YNV8}aydLF3|QZBIRt&>#~k`lvGRK)Tdyvxm3$*&-~n}W*YA9|sd)OU zqWneeq{V*+*$Fmzw>b{I$_(g0L$Ub=me(;oVefXvI>Wy^!hwWv3 zM~@2W@}?9{r><4OKgy@;^;WLH>c|I#tW=UExLl5mlg8HX_z6{)apS`pYJgf686+Q_y3+dPXV>s6+e^5ILDNE#B0G-+nGfaGkF3`2 z3lTK$aYl)y=K|bkt_v3aW}nt8cQbg2J=n=~9VzlmNCtWaDt(1G;w!GAbrb3hqv=f& zq9g%Eer9ZE`qNdb-uGbYzT+U%G`QQ#mOFAsPIHm|H04se)izJHeamN0(pVkQTQ24P z>W&3v&tIj)s&`^0pQ0!P3o`0QRbB^wtv5VJb}wj`Q<2shIJ`x2AV`)ZV2Yp(q-`1L z>t9EKPuNE1Em>Yon|d-?!=&nx##sT0C5cO9qnA)exWH=KNvW$DrOcXVU*KInE6C7u6odSuR>rQhICpUjCYFVSi>`X!_mW zk*Jd-c7!Z4NP~2d=jAJ&-F}#&vij8@X%C5Ra#e$&J9K*&N?Yv(mNGi_82Z?qmp;J zOkB&kDM)exvSbbcV9W3HrOPhE=0HQFWmce!pBIWYfmQxz`Xkmkx529#i-_# z%%n6qAmryAsi>vYY>93gr;{Kc@(2JC`O|P+7=;fZP)a(U!zxZcI$XV&fth3jFqZXP zj%nHV4K1Ln_8UxzI=bKby10>-_2dy`&5xLd+_$Zp5= zs#U6OaOH^~_Ewtb{ym{19S(T?Y5RK1?Gwt028*Zt)U>yLm<`l^W}3uQ?#IZ8&YNIB zBGTWu!9kDbOWRfM!<7y?ZG@ncVQ4tO%8~tP`+BQsFmYXkT^`98XH2$j-h`Zfv^-5m z_Ly=m!<$L88;H~GNC%uQ3H&Jb^(~~BC@#fo%R4(u8>^G2L9r0@z##PgWLC6iR;1Ki z*m8LueI`zFd9QzAG;Z5o?}8)bAzgtB0o->#=e=sAx|p@CiJdNP5UR`&4nQXu`cu{H zEr?z{iX@qf5%+-P{{V$2<{FYE`lE%85a>w59;$tF=~JM~*GZ?}#XKmO8#g`}5U137 z9RC11Lpat$1gwnNAOWZ>SJV8YclW9Tv(hywHC+tpvcH;RjHHk|AD%wWzTp1=AZi+S zE1N;uE%)}Fua)*2X|8QZM?O{w0N^nL*ZJ3zilywQerA&@FH2a`cqU~cJu=-!+>+4@ z59?ff>#W&IK0(O=3-ZMIZA;SqSpxTntrvt#$4L6gIC<-DJ}WaA0D z1Ruewqg7tO(|eWWw049UC+E0XM^9c`=~k;d=uWJRTfm?bAa+yLrwfzqgVa?zv%a5@ zS?t6%+H8(Y(?$Uwd8aIM@68_8w?+60*F{;h>qJqq`qA>(%E{B~lUhQ(N4jEhWJgGa zky<@g%o1=z$$~2Wu_e4%aUWD~~Ob5^V0qZODooQt$I{jN`>oG;gkYO{Mq zhbtTXoQiPwc4wcx=Hz~YpSO0>EG^vbHH~%)+2pvmog^_MM*Q||c2@EkC6od3qixTx6|5cE%`}on&Ki`FXXINLi%994whQT%M<=Jvpu) ztG{fbx}7+zQ>{)hTO@h5l^V-@W|aJq{_(xfVUKfPTZfOm=y^4rqh-0@=vJGhR-a*F z=1G{IJxIs4G5A)wg?C2_DJ|Z?me5u4Nj0L>y&e7Chk~xI9FKJyy`YrzeQ|qcq6tMSe-ez8hZ*>odVGQP3njf7Q<#ij1jkY}eZQW<7S~%3OF7gBY(gjPCIQdwv6&Zkc zEIVTW4)m9pBmyGw6y8a1n~tZ@P^GAH^(v*i#={|toZzz_Dx9yll4R1UB1iM2L%{pp z{*;sGK2|uZ?DGLqgQgml!ptL(F;)br2R|^zPvK3b7DI$&zX~?va3?&}-pDLG0K17F zh*6BusM13Y(AZe=eM(?vnz-zVR5Z%0J!*P`i3_Y--y~xLByw@I0=YR#vpH`Y>S1ArPKV_$``NI24uJkSKY*`OEu*WeFOii_ z#-E0wi%9Uiw^Iz08M&8(#~&#F06w*jE>5-~MS2$)+BY#g5M@`?Zq%SD5C?ebPEYi% zyu#Lu85HAvOttXdu_fZi9kG_;U?dTve8e!wr;evdJ`B)y%D*q_!C?3_LFUW zs@z#xNpRs~vojW)`kkO-j{Me9#KLz;7s)8`xdz)#2Bh zsVmyZ>G!4aYwF7krLfHiU`}})jCS{|lJh0U$b`zMK6`gX?ZL-2T%DMjWMb%mLjcLp zG3)qKh0CgZ=MXUGToQ*m!Wq`HzU<<8P0JAGFaxl+&)?qzCH{i|BG`#g&Q zw{T;5Mq>}0fXAYO2YTuj^mD0sU1HWn-810h2tBKGe$*K zWZ!UvvF(i4p_WB^Yxx*fdK_dsV!ytXf8~@dkEsBEGhXq`Pkjp}gJSzm6OwW3MZmeN z*_kb3pCl{W@WY&u^AGF!Qk1m>&V#~``Fc;7SYen29e4wrSC^Vk-BC57^)=>3bsJ`4 z%5p*GymajLIxN{75S+8`Dn}nTN~bL>4h$X%H~FDh?dZVuIPXnKTFh2sr=B)eP>zHU zakD#dN-Kx5%ixJHjU=enqA1DVuohKF?*VK+HnKy{t0YZ59{HSnS+-gil zxR@})k%P5{>=BA;D4j{q&RaRyxyjEs_Nh@RwjzPNvhsVVVZfzgNQiWH!jgAj4b^A| zDGwM04%X+KarC6shJ}(aPEnAl>`xe?_S{1@q+kbyMhMG+oDagDuVxA9sD|Fz$Ro-n z`{Oz1wH)(W27(()Msj3><^KRX%>lN`2ogYIkSX)ahK zbI8@yua2Ey`cyHYNp>^HREAxllmLAQVt%9AhaY<5i_rA%g(+!wpi?3sRn zhT-(Au(NTJme9mVAeTE`ic0T#R`g>LrsZtYhzQ>(p z&@PE)&Hk$>-HVSt!^dJxSac{uGujruPQ~RvsI)I|!BeJq}0fSM}@GoL(q

@i0GlVU7T#@i*u-0@7)1BJNAcagG=<>rw6X*$30Qy z=LU~#x}*vX4QpGpAyZMh8SLl?>-6T2Zw|3RXWT@zzTU-dUwFaJ+-LNo+ClUVRyWAJ zk=XgNka+oU2>mM6NJR;zM=qIr8CUb=mnW6qgUwp42ctmaAUbxUMoY@W87t=Y{{Z#S zt5g1vI7p1?dV%PdgB%^RfAy-CBAvhB3zl66eTPuoL6-Pq10G=D{#3oKKQFj^vKlP) z1SN!*rE%H8Kb==SH{0$PEQ<2WdV~Qb@Z+f;*A=8yC)gTe!|DbgVK-SmtDospYDw#0 zN;54zD2LZeOVZ^U~Fbz9iZ+(ymRFPF_Vho;2kZAYC`QYd6w6<`Pi zY&4c0jrEaS{zDsC)LB%2W+pleBXd>tY76atBKD84qgm9DeA-aQPGWD$kE>Ara8(~+ z2Dzvfz|s`skGlLCKC4Hp$L+tmD(hOCDR1ng*bj7Ww0&C-{J^R=&?nVzZfxd;=SNnC zZwo-@A}LZ?8{!n>!sh=!tuO=VL8I} zQ(98QLT{B5CDe?>?t(_ROpc>G*Fgf491IVb$9Ei#^j(8Mw-e{f34}hwkwwHyYd#j_ z7|GrDjsc~h$`{b2)?R9^*H+-;98>cGgcHjR!pR$$>{bC_6|#RA!}I&B33<)SG6?O#PSXM8_3uW zT>Ar0nIPmB`z}(s#G88OY5xEUIpQ3QPwbfo0gipukMyIRZXV2Jnq={|8~}a6AIwu# z6Axz1Yh5c#)URT;SS81mFpWIdAP(Iz&{kD>cSv+sJk%dk z)i1UC`8+3gE6kAZjotj9Mp%yLImLNDS*a&wwa-qb4wWdmTJGIX1lHb2H01{L%F;f< zfk@yC^P2Z?)AqGFr5jlJ+4fAWqwR*z%)l$9S^PPYIV;%i7T@@*F!6>UD zaCnH)O~_9e>AK`TF29i`h(hIF6OeNr(YN#!57<-pC9k2!UaYFk%GNRd$*)|hvdsgy ze<4~uDi?x}Zr_hDv)}d7ZTQFw;>?5zgrD&svmvUUBorz0puB|DG-ZswwzCTRV z+YGeScb7srF16^R<|wv9iVf6AdfaNos(d=p4;s-5D5no)ijt-wNP^Sv(*V3X? zVz!Ve5~cOV)!Ah(!+;xb13sM8Ix$+g70!pvcX1rpOIw9wfwyk%eU5peG~(9iT*z&{ zp%lBoS-jj0!G_#XIxTB`3+7fDH8JyvE#_0f<~)N(vFyp`M5j@=50yqkjBaHh0qs(# z$fYDgvuT=~s|CgU5r7@k%)~C_QgNSCi~-z{UD#SKF`H{!XeRFc)2{Hm)=S}k5NgxR zk9QnDVz!LF8!})P1F7VWeT`)b2~(7%xx8vhDRTAw4t-2XGY2t7hyX2)-j&AU_9L6Q z6#CtrtWPYmL?aRI+9M^7e(z4_inw8CH)Mg-vee@sw|#qFYx~%m8#yG)a6k$=C_jhu zuUb-6DSJ3OvlXkmH@r`ABk0LvVkMML*?%k@s)L*#$EVV`@Nr(#o~D*6q_nad;j3lS zbn=%?<-NmhY>brva6cdGU24_q-E9(6v}}9FgY^A7>{gdQWViE}Dyx{3vXC?Pv6|(n ztwXJ=Lg?duBVK*G#Zp`OXz^MojSj#`C+1_5?te<&Fuf@_?sH03vS%cT0U^A%MrObf z@L1QEX>4e!SqzQ_B_u|kBpLa)j()UW+6OalMnBJZ)W~jRBeF0D^7Wxc`dDtGMR9*D zyI2P*-zje^wO_MKar;NPWp3_RZH6vWh1{fMW7F}d<&E`Xby*tT&H;IC2wkHfjy);5 zap=X)?XbQVlwNE4wwZGmmY4SBVn$p7$=mYHZ;GeOMl*}p?ZHa?(U($&w{Icw7LFxm zL1AkOA7kdQAEz~VdWq1JU2Zz}_VGO@Nq_j6z>WxhWh6I10qgnKknGPz=$XMx?K4PA zCQC8NInGXNtt65>S+wNIQrtLL*Es-_#&h}7y@5n;l*&qd?c74TmOAMI| zBf$V1Zf?HSE+{&ZnTg0jxxE1$KD3hVJF+jfAqGc3G0x@B{{UL4!M%y*u@tvK_&ss_^s1iFYXvS1BZ(YY7v)lEfm$M6vQvxTJk@;bq z9((i9Q*m!r?iU^vPwvnaJm9GS_o=r~xHNC&6et+=Ab*V!+$>fIRR-Ac7*CV~(EV#x z`-^hQ{PE=!$G57tIR>+m*27)Ngv2a+r0ePdsd2dOaXvP?SJdLRRptn~=Yziny{xvI zjW;glQ+H=QX*su%B~=BJrh6LoV9c5{2B^1QT3^DmB1fk~BVOKfW2j4UWI1c{uDJu&K)N=~VD}$05<9tMk8AM6atOg(W2fg$(~8&>nJU~*)tCW{ zjGUaDaDNVIII9gdGHc6-*v9dy)nTd3O)A-a6zu6&w}X zbBbv}bPKY&YEy04B!DqouH}56Z>4GM;l96lJkMc*>ry0adu^ou017d|CjyRIeVDZo zfF203Q~uwC990rR;k!T;4R&M{{Xa0Flp@rgU;)6ITR;JeV6x!tg;2f!Pze@;$?h-A!G9AzddSw z%iiCKaldj8D^LhpOZ$A2mHp3A?UVXde($7z5j*u`ToNtiODT1zwqQbQXR z+z%NQ-wK?`O2jyl#M4|(751hS@V`pZ;va6T1lTHY4;ZE{qfYPa>2d}$&>wR^7*|id z)DEF&KtzXO$f^TM+U`ZPxQZu*eC5M=iV4TDJt}7fCYu~xjn4~d`kl?Ah;Lv;iM~X3 z+()SZcCSAfI&f>6EYg%*wl&vI)?<^&Tf1_4o%jp~w-w9nXtjGYUuRO}@1<(;vSRwk z5P5Y9G5HMC`+5s>$L#8KBL2_UHzcM_&Aje_5f}EmwPk>Dt;N z6LA}iWEGbu9@M_ELi-JSR7keEz$>=SyO5=h(e#R2`7m)IN?W%EiTGYEzjq6@!fvK6yQE z5?$&jPWExfrxevXPhoR1AGF;E;I`w~Rr@#!{lMSewphsfn}SK?(d?=A9oWCxH)=rh z8}iK_#t)!fgKBqSiD{(C>T`-cj2}T=k4FbJ* zsz7}GhezT&oxrvZ-k8ARl(3L{2}rWOE4cEEZ4oQz4l(FzKBW}&W&2GwB7YH0BxM2| z_s%&OpxX3Kj}ccjhOZ5y7r3Lux?4 z1M;6wraDzi4BodFHHnj5iGJ)ZzwKwYrAxv$)H#<@&8^~U_c5SMF7;9e9c!}*OW4Lu zkK$hXrypW-I#ZGut$1gj_c$s3CYOG>&2dt`O& zb6Qnx)AxN3M+GitXV8lcoV&4F3RH+4&wv zG+7XdjAB-ChQUA>l6_BJDy}VAAxUFh+f3O|7d;o+r$z*iE3|lY8`*MOcds44l_Zt) z2Xln+WI5L!SWxX`K=}UfPz`(7H6=w{gDj{c0i-9r#~7yvmY@e7zzR84%Oi|Z5lh2 zT(%|^_O@3$ubSndR#^rY@-7_Ophj#S*Jg#iQwV1Jz} z*a*ReBob`_l#{!l105;q#bhSeG6EumZRZrUu&nMO@|6`Pf;mfP1bfpC1bC#{IGd zToMx_jEr&*S`|5T6L%$&;x8z&vO44szC#awX(-D2q1_QHsg)aceHF;S=hmLKu&zuc zMcT$C-cCX6IK~gRH8y<*bVg=~%uq?ZbDZ!S9>1MCNi79*K1g9~h#f!}+Qt6>hLUny zhMfswh;1z32392e!;z2YP}?aoJ@6_j^i~+@!T$j3^s42P3!J};tt=YdFYT}=X9(DA zjkxss*Qb}%seVapWi*aPHQgxzfB+InuUT$pYTD^S<)KaCmOwsq=bYmh_3u+!r?ZQZ ztqOkDYA17|{5G?=zh##Bu2mwGi1uv-fEdmXV!-3RE61yt)oRg=k8Yk>N*?r`$u#YL z*TS-D`d*7}lB!v?mDSwGaGA?D&V#Tc<@Deh+7x}FRVr@(00Roup-M`e7sAu|{<@t0 zqXWZ#ZzNJVL2C-fBOjQBP`u;)-Ycp#lxk?sH$qdRrltKR>ob^JaSo+q+Ib_?d*>gO zbk^moO~QMeu!~&{Rkw%bjb?n7AwV5**ERDkyS)ujESB3-%x%Gj)l~zj#U(C-YpEEH z7j2+UuEXb1lhd#CqV07-VbQ@6nefuM@4u1Kl%&OFanahy8wr6K&&qi9_UlX9X=@d2 zMtNnKqh~F^`9S`(?|o1>+j1w7B##Rl1ugS8vFY^fRF_06bu1Vmk=ty5D{<4edY36J z8A&GYJ)nUVp+{wQal>b}c+DKm+)LDka?I{lCSrLRJb(>Sw{T*tij5kG-FWI7p{cc< z%1r>8EsEuTbgQ+4zZs{<-owyFyRw7>y(8n51&#)J>BTilZ=p%WE4E1`h^fn@V544iiWKy@EGj0+jkMjDAew0cxYhft_^VwRuOK)_t0y83S z0|%!Azau}5Ql%!fL*|Mg5?K~j^W)#2m;;P=2mJa{<*RLpazl;XfFK!TEB?fUWZ>ZQ zOWD-yE@VKug`AAbB#5fGD&OA0=~e8iC883Yi4#)O{?I>uilO;nPa_<0-j8Ebu=$A- zT+?#9WD3BX5)J^!K?e(n@B$nP<`T2Z&ryn&m1vz9tW0dba91qTwz{Gak`S!&$^%#WW zn2GHsFZiQf~^N@T^%aAu&aHi2}y?ivIg&z@PE!~T^hF5B`T}Ap$+|?mHz-A2_ABC z{{Sl<+#dZaCr+jHMx5((HGV}NWVZ6CT&50l+mG<4@?^I68#K22oJ^McP70Dq9C{wy z(eFJ`3Vq0=omTBpyq}o{e7=2w2O^wR)ylkYv21y9gw33zIRTW7zlS}4ohF%F%EE>- zVW4!7_fmv$_029+w?%Z&d9nsfZH5;-dFh*M8WXZmt(GYf+%h6{!}~MZmcOTN?>PFX>M$0|ZL2bs|RT|YX;g({F-(OZi!#Cu6{_A%_PtS#=Lxr%3sc-tz$ z&V3KydE&K~w&msMa#5=3OPL#5mx}H+YY($sU#nZIXUbT@=bkGi8jGQIF%F{E#(Z}| z-Up5w`J+ev2;?fS?m#_$wO5+xV5IG2#>XUaw)M;NmSzC+>U-3_XQ){NLmJ2BMItP0 zesQ=A^V`=oeDmrqT{IWUju_F*W>h`aLC@h?^EMp4$s~daGxDiC0L(z?+lmzAnOus+ zS@f%D*-TKNlXuO|SMaSBd39ptNO9AwWWX@ShZ)Awe_EGbqS=d;SQALG^W+8~2GTMC z!T$jDQ~J$KH4a3I(&;wj#~+osPe1KdEJUq)ijunZW0G6Gex0cL-0isin-pl4@Ycodhp(~ zNmuQnC<=TNvYLtBxvm(4Ilx*fw&e^6a5w-ZqSYGy4Ak_33?TqS$`S zN&GWzr98PL!5Ji`K9trq^+2f*O`<^*-dXcL_ud1rt5}&mLDLdn4;Db^;ZJbKBN(DM zyE`#^KQbTqM{YN$4y9OdMOwwgT#Rg<9=ZoH;zR={AI0>i98@0Q;K&Y(exMI1_34qp ztNO)0;^bnT8udnemtNU#){m@HzTwKnZ3j`eD77UFM*rzbU`?b`Q=G|eBv*Y0jrc1Q+%wga!+r+BxxSa3;Hf#t|?=%$t9++&cqQb3S+<3H0aK)A=OXa%^*FGgEsIN2Bz+KauTiVAZf2vEV zYFBq}GoQ3f@^U*CBoE58r%gpGqnfPwt2=CWJ}KK_@YlmIA;gkO(l!TNC;tFmxv9lU zrQe_Cj_hQbrzHOXyZW4TI+Mq?MxQJ=+qboCDiM1l$mVFV{jjed!5AQ500ukc)_+&j zzI*5|_Pk^W%s4@wHxPNF*mVo^7EM{)?PW(+0AtV&f|sxok%N6NnRM<6#&{Xe74$S$ zEi{f+?<24AoL2VH_)|l>l2o^}wGvJu0I_KRJZHZjtz{Q^*k_{ncw423zSz4hGv&wq zz>A(wT=lHyrlUueKCvCkG{QAjB)`fC=dV81<2qWlb*brMbE!%qA~uU8V}XoRdnnw$ zXzVf@sn5^mDnA77C#^X~C`wXb>o)Qf!77zGWIYaloh8fGs9-{>{oUi*EeojIN`=H z0|GEkY4X7nOnouY7yeDo|T7IWj|e z=KQT@jmCIvew9ASZ*b?V{J<9KE11Z5kQT|ykT7XFQjV#L=!!vaGBSw9alu!{ zJ%v){PWK0?AWz%e6HzzJCMJ>{Ghly_^5uzB_3zTUXi8NlX-efrN-tDu zcxLNFlKxw1Wrp4<&+g`C*kh0`Lvz^u)AjbM#Zjo^cjdLa=^0e z&V4ac?Bu#89LPy;Tr#QD~H3N1)UZee$2 zOT_uU@Y%GJpXtRvXDfUmewPVcmi^u71Q6i)W0FPWIj~gf#b1qN#2Od&Kw-)uhOkbXima35TtLibB^ac{{Wu&s#PT3!D6|yjy;Sg zm>g#+dV8AHRg=1~axKW78)s)JG?~T&IqCUP%{RIRqKN`oygppB%OeAj0sg1*q}4i2W%!2~}7#ELTlqXvE zvLEj4ViRwq<09_sB}Yh?ujkUVQmuWFJhZL33AWP)f6{JAC;oYX#aA_Mi4)5X;(0nq zU%zt4jyM!vzmTl~;OM(pG!pU9gHq#&?mIx93A{MDX{{ZIIRS||_Bb^Xjw_=CsgcPi zhuMoHQm;>x6OVcl#anP{m=WQ_5G5`InaRTv2&%R#x+N$OB4{OxvfOR-%~Sf$+6kzV zBpL%L=4BvpgVg)`R5@aha>a@*ba5$UVIts_4Uj12#0M(wLZC4FhXsdB9sxD#M`dzZ zl{A7I({cs?IAPO|{F!0$>)GzP%Y5%!Tf6=mJSdsm+8wYn)r zCGjG@(xBI(;Zl1Qmu|Srt2nwCP-~$MGDNV&O(rSRH-MZ+9Q_A<0@7# zMh9b88ONzMV)=I{n>XzQ{%A=j^l3K4nx_u^zzze%OeJ2>HN`M!9c| zavP2EuzQGma5(*0aKIswZ|EU_ySqC!Bs75RgAMcw2n~?^_QWF~ z7)$a$gbfRE$5Q;szF}?@e@_6_&(A%WOduUo)%LKYJ=4SxH< z7f$;|K8OK=5Wid_zLE0JR`+MmY|V}JEOzTd5gUK1e4CZNdVg?D0rR+n(bx@&`GawEN_m2NSJ3wx96ANQ;fn>&{J{tX z@Pt0b6Z8)Mbp&|>3pa2d3ferr_xWvo-^PA}LC2a-Td(i42ZB*#a38h-gHG??)5#6& z82$5g1RTWB&_Fc>1^*Cvl3ReQJDrCV4w3>D@bZcZkcRfbKoZ%Xn2yL>>frvhG8RaHSzNkK_T4)l-EORlpyM0*?e5!rz7#rCHD8jyr>5vZIX8C?QkZY3sMZh2|Ah#{;=VQfPVh-- zDA2L?rF@@pg{5edC@x|!e# z#4|QD02mKg+z+DVugoJUU}?B10E5Gm624(&1iQNdiAy&@LcJ&<^wteqBp}oaOcF>T zhlT+=274$VjBcK|$^VG=@9_Hr?s}w9ch7*}Lkb(`^9Op`279{ulMiX(e}weQ!#@Dg z2@CZK2o50zgiz>2_;>g7kHh`|Rc~X-zmvmm8=FCw_djCwz2hH-TL**%limMaT)y}I z0}MS%=%IfX!baaejJFO2=fAN&3RjCELG;5RK! z^8J1^DefUZ$j)Ch)~_n!&-nfKV)|F*_ygPhg+~AVz;AcI%=qI9{u<5yNT!m=ZXxdO zzZ0k%Pd02lU=opk6sF%gtLX!=KtDNQA;064KX% zH^33EsHCi+@TW~1oAF!A-rol8+_CG&F+a5YHpUhN9rWm-1OFdw|6h#BU&Y&>`zHU& zG~68!;qOaw!}`&kvv0=B-XV~<}@BiD5{Q_z?kYk~g$R8Bl-@y89 zz<+v}fxrLfXZe>vz;{C%+^U%cxKTU~{VMM03i*HH`G<~wf!{ZA2%LKmGEn$6X7Eib z)7|R-n;$y;|F3`kJIOyH@qfwnUvm8;3jCwa|2m-SgFFQ?{}YP+byV+fgN6cde~9-- zV*i$%6M%>O4K|@rdaKpZ~L&6XQLxLsJSNZ(|gCiJ_j7-cdC|1y+jthdr5C}K} z0*RzcNLUnThZwk#n?&$BjGHY$s3Mez;^4GWJrcqlUNXnaEAv83dz z6WKW@PvxG@D<~`~E-5W5zj)9HWtOnYh+{w60Y||{d-+iio^bTtKuo=$7c@?7bWKUcL8CDe+Yo(H z&hqe>1SFpehUUcdnt-JpM#52gn{qR)Ft9uq`R)3*@FuT0vi;%KJRL>wpx#`jW-O-( z!qUQ#%NbAj(pD$pEEV6HfTuv&ts?|)bettXvK!ButP{lKND7w|Nl>Nf_V97^u^&?- z!U*w&L^dLSJmN-d9s?R8@}Urx6sY!=dJjB=(1vD7N%v~(m?6}m7%{Z1yfOQdlI>Yp zlU;B$l0gAtN|!IQmJOmgrWi#bY4YgSwb&S+KpIyqvX$ESvn#b4MnFPBPie>wggqNk zTn-k9DJ0sn1rb=2Atal;93)^_#;98Gs$-Ub6p=|uMi4vHGf0PtSTq~n2uVYrA!Z~mR0a~8X2Rp(j=mo=Z6pUsnh~S_pQ~B0REG;sOjfkaraYjOTBSb?p z0)=$xQf7Zf!c{w{yHYn%8w+9_@Uw)MnYC0CSUiLVRILzHBWmhWITonw%$RUm6rvf5 zr#Z$WP>?ziHUOSOMN+oitK3(u>?U z^wGB7+*WF2%0TA1*4)-iFZ3-bsQDAkbpHaI`YWQKUNZzm(2!qnT#chK@o>J&%D!-G z7=_aXeJ7kZvkmQx?0_|f5Sn#kQcU5QC>(p6Ko1;juoU%XybKg#3dmSX#jzuLbK^Ti zrUk&Bv%a7XS^_k)S!f-IY9hQxqBYYQhpB~JV-9T@tW0gqW!zOHi1NXowX+SopiZi2*8arn6KcjR;?& zk&JXqHQH`Enj**3;H}nNM7$UD4jd*waAVyQpcp=FjLna*btQu9T!*v|48r_oP-@F z@}gi!Y>y)Yx>LZano%mbbu=a=8A%G7F+wor&S8o~yZ}>Ky6{rLpg5^bL&g@_wo*|H5;#G?0C8wGPGu;U2B(senHnugNC?fY71O~^7Z>Em%Fa7DC=d|} zGN7T6ZFPuExplmmtuYRIy)g@XXegVC?*?qqK5M#T^ZR0##@fW7!`7?`I2ewrHbFQI zIy!6WqA!jWjY;%&Y0a#H$Hq0o*^byalL&Qe{L1XR!K{sqMc_yx8WT^@QNa@&)O#Qt zSRsErtjE+ch?TI&X4&WUXw>LvOky1zwqgyAMNR^` zUBO!~9EA)-vn$e&g`_YPtcC!l04rIf9gYVpdcudMN>OQ%L|88sB@17qaS_=?f<6)9 zM3E!e$ao-!6oF6~qN2z;1lCVIED1P9c^vB?INEhsz9A}`HDHCz4tQ=NvY_P+=M|pk z+l%xD3{cvR;jBegC-(LhXuRCrwSGGGNwBclTue&f&pLetAX_Lmg zSE`p(FI?b6IlPjR9JH$5p)#~9@ts?`_VTd8$5IbIJFMEt{L(WsKYiAU&f#U>f7^5E zMxOTAl*J>bFSv%>S^3s9tM;=jAHPB{WiNq>*t(ibD+owF@OnhVwPx5)KRb@iZq>Wgbu+A^Nzr3#Gu?d+9TQM8WRr*PCBNPHD3!d0_@ui=b%u- zi}-k{jX=qHBl>_cd68xin;Tz*_W}se6&LFwO$W%qNet~s?0PLzW&~{q$94l64h{X> zgYBwB^l0+~rlwlY>M{z0V*!LX;{^gzkq;;|Z>C8(MY9B4BP*Z?A3A}GazV4|1QAe3 zSWuU5P;Hi4fx?S$5yI*T{n$0OJr8*to{jlgRt-mUsgu7vlzcCo#wV}*?4=&#O#8Ls zJ0kuXN7rW?pRF#gQFb_%9M78T)mq+`yC`d{eY(gy=VUD6p(8}J@Fo~#WgdPM#QVBY zfXLP$*ul&1K3KIkXlm9<^o>1M;JxP5M@63|*8>u(c)^zAr`AsIHTshAvG8<~ez=mq zhIkdL!*lpy!=SHF2j{fZ{{6+Dn?n^Z%la>@!1vwyu=$qcTtiPfJIXOGWw;6x*CO`e zV&9of6}|F`yaN7~T}wA(8-et`1=pnka@Z0C7p(|6D3z^oTMu4(0_E5(#YQ{yt_?*IWl{-L0Ih znX$k&5t#$xKr1_rh~^BWA<>G}LuFA6Wv7UI4}Tt_ru**V+h(tw zw@}=?>Or+X%yrJwY*Ic(>vq?wrE8PJSikSqxd+i`?{GdD$vxNYKVL0)v?HaTZp3{o8q)7P<71t5qtCx?OT_d0Ze7p!C*Rbcl=Aj2D6DZ( zRX!s#zrvPq3`<7Er*3a6>)aEgn*b&|Z?kUu2!?O!J)5^qW&~pPf!Z zQ9dm8B@G_Qsh@cZ8!aDoM#Z~*$bNLOwfhw5hGpuTb)GX_)^G1QNNJVbIF)I}oow@R zYt)-i2I1CIOKF;U?y_vFMt5$!PdoQYX)1yxK=RA`ob=As&CAgO8O1&VVG_+xF6ZWR zl-e!}q|KgtaX!@VGWOs$;nwF}Wtz;(JonV*%L_?wHP7qaxJ;`VVR=~z#ese1h#@cy z7a47^z?rxU$i4?`d}63geo|k{lrzUnGK`HGd_Ctv+u8;i{=UinJpI zQC_S}k&txDMbW$Hpf=st4UL`CV8xyB^ECOF_{2+rW_=(@(x zXil2CG1WjDLr2gE&Q|I?XC*9L*hG_{M&z55!?J}C+JSwFmx_-s;!liZKs!VL&EErG zB}z!5V^S=1;$bw7^RJC`2(!aDo;XawGP`abjKVQu28>sr3W13wL02xd;M-Wl3-m`f zbnCFC%PqXRd~0y~Qgh3r@Dn%-&kMFll>zlWcrsUZ+E@VYzp9esOMT%UIH?ol@7f z_wA2J&z_$W{TcgSd%MFuVe2a@>Aalbj)!ySUe8a&JA{)m?I*p3)Jola1mv{FRr<@) z%_?@S^9p#>m8M_o-(u4i+>9E1dS*`T>uq@i=tE-k_;X17JKQ1+feR0u{vMvxOpWlho_I&PUd+i{qdBwt+~tv z?=2a+2P=fG=au@Un1l@5Y`2-S5Xdca7j+Nh&^M->96bZs+}$Q|AYrUfaMxnRkm)1M zJy%lv%pbpPsF8gX&AAH~WwWGQ=d^wI<44oUi%X^LNCYo+Ga4f2A^PwHCS@wHsoT)E z@J7e+rlb|X=>$w^P$HU&CnyUdx5md(H>I#SLMHG@Dae`uoq#EUCF}H2 zSU!L+0ows{p#kWoJ0Q&|XLv5qP*^^UAn+&jsg8)if?GhCLD}tcBJq%dH>6{V>{5 z;;UYdryo#~!e!gD%#9_pqKwN`jMe#jaCVnywwA)iwH0@0JaWmd**i=P$SSsKcTP=(llp>Qi6<+@2#cq zI$B9@p$zAkNmvb6Q_s2u5E%aZ%Kv$yC6=WN5!QB2pk3=g@GB>LuAH5h-64-0PG~>2p@S_ zj72d@qTh;`87*vXRzyK2jB;>Yva&DCh?E{BMdgQZkf0Q^6g#uNdK_@m2-qUU5}A0B zlw|lC190eBlHskrye&q)CL|XcLZ}aN&15C8m?9Uz&Lfg_s);**J2OS7LVNSFk@FXT zkpj^jAOPtTkspcW%|#;EEQ6RVlNnmm4CJsV7sxXi!eWcN7 zI=V1@HlTUyAaEqCS?LC0VI_7@{qZDx@q!l{j+GG!TBufb%AVn<uV< zeF7c4Z|baD^Doc6?6C^CP)qVNjgHJ}*NBRe$*9Te;fZ#eFB^ z#Vwz_zJKT7(NT+>^}=Rb$)1uq*J=*tz@o#c1C^;;l4MaGUNUC31h1u}$`DGLmdWxr``mSbRb}q@I?pyFLbnI|P;7uIr%wt~M3SU$ zT%P|5sTH$m9?!m{t6+DazSk*mvvVi){D@Wm{!W7fj(glfcjRwLaNp|hmc1%Mj27E_ z&2HFYt9%m2DgA~zSMhW^cTK^X)gawszP{6B>E>F@ygtm>&rd``tLEC=3$4-%7ng`m zg*%U3l2c8)=QJa8JG%n^x?!$Q>2qjVXhFTM?f8OUo}v+vDsX(U{EEceiE5u|j;*W> zL%E&Ti+)ls_zL|zctJn@v6$1bz4e#o$UQj873;w8#=R%m9GtI8AI7(Qxs+bABW))| zyN|zqWNdR!`4=DUSy}DqTlrqge78?*VLiWWq*4?#c&J(826^1VWA{rLB=%Aycig_9 z<($XYgs3)`Uob=&NXZ%WoVyAvBm=Bofd!t1l^qjhg z7nc;x1<#KD5}sRz?C)SZMHOI;ct*9z@p>W}AV4H}gY z6EPN|c^s|c7dW4!azM?aI_4`BvSn>Vxbt;GPqf-;t%Saj{TJ*bI*ld!Ker8+uUJKb<%Xs#pe1&_)4JEC*b8qkM zOAqxtJFvWa@3FSzom=eY`u25g2MM4~Jo>0TDQI(Q3PE)v7W!K|rM z!AS_UyrUc=W)QJpnMZLp_g1@V&MvSva~i+i9u>XW(cr{%@I#(n+@oY>zr;CL8?9!G ziGe!HV&r=K9 zy-gQ&hiIv1Bl-niG?zRq@DA{k8fNAcNa@|JiLtdlucmx>x1 z)nvAIzxt^^wSYTnOIDcv@%m@&PHQDo`|ExRm73xS4++ka$|rw3k)e;_8MEzj^{mf7 zy3@7Qsf+E1ZyApTgxq@eb97qJwZ@V5vKMewj+Q9bSH7PR}y1M{tGj+{TQueZINFE^{VihZGPTy*2C ztNLXL#e?aJHk{>5k{>SnFr~E;?LyC2&e_*pxw4!5*-GVB=3OS=gom#VT{g)Vlc`u- zK5*{IXyU9jw|{5&KF5-G?zq=NDGgjbdnk$qJtEU9-E|1m32;UDAPO}x*hkD77rg|!vGiaZ>KVYs*5nz!* zz$pfnN_$r^AJkO`j0i4zcu^-A7}|+b1QdnYgbbWMW*mke{>lrc-h&S^!km+ugQWut zh&5)$NO>3sb>cQP5T=X()9Stk<_$kn;}LXx8Tm;bBNzzLj>Hf$t@u%{KmfGO7qQ;Q zv8w@f0`ycbP&hp%ko6}bN6ShNoCsnrDr_Dbp=uamnK#yV8*@0C(_4pT&Ax*-lc^4Z z=Q2eyz+;9jfz8CAaKj6OVqhv}CJ}t)r65=*g<5iSYhoni2*M23AZig$^9nAPgskE5 za}8vdy{xB8gy$c~2Sz=kt(ol!msa#`Y5JhKP|CFuKC^!7Ui3@DRoCLC?fS-Y_wQVm zX!Pxt%@ST3K0mQ{zTR<90e1N2bKx8<-Fjic_Dro$;s?(L^Y4&09NUvEoBI@U-+QW1 zKAT&qUMzz}4g8vX(2mcpX1=(QtMhymR^>8+U|oD6ZA29?b*+CRbM7eNv8bEl4}tpZo9tyX>&7s zfJu?}jm{eb#IzK=L1x!jdvelb;pIcPGPJ}sf=ya{6mO_!)1k0qHh6k%z; z>OEUQj08mPkgYK<2}LZxxcfxJPQ|3^C@9 z>Im%V&zN6UUJ|-YKGCmXs5w6A*P!En@GIoEURu2*T)D?DuQSi_>gK^fX)|AeMH*t- zt4}RiA8Nl;&S<-)V|Ph-)?h0{U?uGt%8UX9FYVSsDZjjx`wlYRR1&LN5Z z6{7IDhdN)OC#9GsO_uUqkL^8m7`Pof#@ZYAzgZ-|+h>`a-1G6GS@#RQq=)D`o6i#4 zTskBaq5RmUX;;eQ=ch0`b}4A&y(&*X)2k+?Zx+S}g;p7xSVf#@qHL??*BiufOW(iS z$fHp{%`%vwsw%1C%A1m^m!91bygkS7xq;%5oJ#4lS*CJfq^35W2ZxiF#KimWi&w}? zS7F~ga_Pf^1c){F);*Z%Hm&}%7hBb9=5Msuhs1=Y#ZmR#7H+HZ9Q_bFOItZ$T~~J_ z&w;cR&yTZtXc`rb`dFG#EV#+H@$rd)3AxKpQ})@`msv`$dsBq50`mEUVRVj+-?_~ zd&(A+ zUrr1!ulc-eo5*p=_SRQf-u%G!gjrK!R=zjqXvX=+m4^@h^o0Gup7|U1H=AbMG2GG- z@jNHEyEa5$=}frDU_4@Ga5cG&8)Zvr>`!|X7T_XcXk6j6 z9h)yl0xn~-+Ds2!1reMbbvFz3b+&>i)HE6jD`C?kc*$WQB`^Or6oaHUfb$m34Ahr! zSuL{^n|cpF$VdD-S|;_g?wN!h~t=c`Er^6#BSFDVvDAi^k{~+GKRYY4&n~-TZ&1;y4#1+j@I$) z5xaDut(>j2Cq3cHxx5;q`g()8WyLRp?oi)Eo~3ey#^zE={+1`=^KT3K{LW@^h_j(zpd<$7?@#95*29Ev7vtlE4k*Y8!Ox5A=NK*(3f zc^o?5QsekJ?5d&i{M}0O{OPetO2Qeqd!3*g}hsnzS=-ePK7{S=>=RSIp|GOS3!s{6Tn? zNtsT!!0j`v+s^LPp4rT~wm5Qgb@6%peV>tqlxuH0iyk@ey;GVbzszJryrTGeLPOrL zZ{q#sBkAsYcym;W&g+$A>7G-JmhFkr!fJ3Rd4~Ii$r`C_ujo8{=`3hycEPZ)#Q6=>nCaZ1R&z0!g zR{GPG`?9BXi)>!arE$IJcR}AWfwv^XTV7B>CL4)$0?uR|7NpUMFrZ5lFf?759;T5d zzYTD@4q#h|$ZVnq$MKbgqM0&L7^X7M2BI4vcujmatCeh%^Fg# z(;Q6b)qb~g%FMO&jl#vY`=mNsm%9^UHCq^K6O=3| zg9q#~bT2v09zTBeTE;adDE;Z;XfuoJw6WLQbbB-1-Iu6T94rdDRps+Ckkf<#e{J*< zjqO>US*lfIStFk8nvMxC>F=0~+!+^!HZI&tJ?J!c+(INfm3w9Le)oe@u@9 zi`wki+&g??$#_Ub^3#cn=Q~W!cQ!Nc$(Pn~)VTV%skF%d&?H;()|6=6O8$i8up^x{zEHTR!&$fLOU zX98_r2&F4!&+1}px)7gYKN5$Jr`cl~aQE!|zGz>af6GtKy02tBz2{ulS4hy|E41Cg zcshJUiYGweoJYWDEE60JiXhrEzf;x&xt$giGoo+M*Rt0=k1yHv)5mqv`dYP$3YZPQW@G#&-{7;xvDt~*Q#D3j6O)yXpd zX8)yyuMq0XDkiy6dfsyAGgEDCMewP5YX@gc2{e%t^eD0Ha(SC8=~$u$`{-P)%=k4% zc};y7iO!noUQiL-nr=hKB;Ys+Q^5aZ#L{$?ykQd20+X#UUIh zX+p9?he1XQ4vI?Pz)9vT2G%tmlxVOT+(I*sfY=zQQRu_5$AZcaHsHj^xvWXW@ty63 z5xUh8?pdlX;4Qr>vd>{(Jn;6hv`Gw@e&Klr9u^B#>?dfZ!N}5 zq0pHQvR$pzyQaWWsb=KV{xE0Li8twGR;gi|RbndRloN2EcuCiT+Lw3tmE!^Z?NPVF z8^1zs@1wikUr<*+4QTB(m7L>R*(VU?bErR6?EXVd{RWcm>%0#iFsE&g7(7ZoX0K|D z1hF^(aK`aMCKAf`^}q*Xt%cjI_g?o&`k%AnaOI8SbWCPELNq3v>N5y>>An zM?QLPADJa7`x#8n?_pgTKl8>u(ZAm^RXF69rjL~4a#OZ+ute7I?)A3%-glx6DORV5 zO)O}Mr57sPV=dx5t`S`L^O4YS32-tp_4tNtsmY-b32M(^9r^tL;+U5H!Z|?vzBqs zPN&<6?_=t9K80$>e38yRUp`@bZ}o9>^h$1x)19RlgpBz0`kbKH(qUTz`@zvAC9AQl znup$e@XuCxVGmE54|WwOu�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

xa_u8c)hsQoXBakm44Rj@Hu~WCdpmw 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�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

xa_u8c)hsQoXBakm44Rj@Hu~WCdpmw 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�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

xa_u8c)hsQoXBakm44Rj@Hu~WCdpmw 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: animalgrav = Fixtures::get('grav'); + $this->assets = $this->grav['assets']; + } + + protected function _after() + { + } + + public function testAddingAssets() + { + //test add() + $this->assets->add('test.css'); + + $css = $this->assets->css(); + $this->assertSame('' . PHP_EOL, $css); + + $array = $this->assets->getCss(); + $this->assertSame([ + 'asset' => '/test.css', + 'priority' => 10, + 'order' => 0, + 'pipeline' => true, + 'group' => 'head' + ], reset($array)); + + $this->assets->add('test.js'); + $js = $this->assets->js(); + $this->assertSame('' . PHP_EOL, $js); + + $array = $this->assets->getCss(); + $this->assertSame([ + 'asset' => '/test.css', + 'priority' => 10, + 'order' => 0, + 'pipeline' => true, + 'group' => 'head' + ], reset($array)); + + //test addCss(). Test adding asset to a separate group + $this->assets->reset(); + $this->assets->addCSS('test.css'); + $css = $this->assets->css(); + $this->assertSame('' . PHP_EOL, $css); + + $array = $this->assets->getCss(); + $this->assertSame([ + 'asset' => '/test.css', + 'priority' => 10, + 'order' => 0, + 'pipeline' => true, + 'group' => 'head' + ], reset($array)); + + //test addJs() + $this->assets->reset(); + $this->assets->addJs('test.js'); + $js = $this->assets->js(); + $this->assertSame('' . PHP_EOL, $js); + + $array = $this->assets->getJs(); + $this->assertSame([ + 'asset' => '/test.js', + 'priority' => 10, + 'order' => 0, + 'pipeline' => true, + 'loading' => '', + 'group' => 'head' + ], reset($array)); + + //Test CSS Groups + $this->assets->reset(); + $this->assets->addCSS('test.css', null, true, 'footer'); + $css = $this->assets->css(); + $this->assertEmpty($css); + $css = $this->assets->css('footer'); + $this->assertSame('' . PHP_EOL, $css); + + $array = $this->assets->getCss(); + $this->assertSame([ + 'asset' => '/test.css', + 'priority' => 10, + 'order' => 0, + 'pipeline' => true, + 'group' => 'footer' + ], reset($array)); + + //Test JS Groups + $this->assets->reset(); + $this->assets->addJs('test.js', null, true, null, 'footer'); + $js = $this->assets->js(); + $this->assertEmpty($js); + $js = $this->assets->js('footer'); + $this->assertSame('' . PHP_EOL, $js); + + $array = $this->assets->getJs(); + $this->assertSame([ + 'asset' => '/test.js', + 'priority' => 10, + 'order' => 0, + 'pipeline' => true, + 'loading' => '', + 'group' => 'footer' + ], reset($array)); + + //Test async / defer + $this->assets->reset(); + $this->assets->addJs('test.js', null, true, 'async', null); + $js = $this->assets->js(); + $this->assertSame('' . PHP_EOL, $js); + + $array = $this->assets->getJs(); + $this->assertSame([ + 'asset' => '/test.js', + 'priority' => 10, + 'order' => 0, + 'pipeline' => true, + 'loading' => 'async', + 'group' => 'head' + ], reset($array)); + + $this->assets->reset(); + $this->assets->addJs('test.js', null, true, 'defer', null); + $js = $this->assets->js(); + $this->assertSame('' . PHP_EOL, $js); + + $array = $this->assets->getJs(); + $this->assertSame([ + 'asset' => '/test.js', + 'priority' => 10, + 'order' => 0, + 'pipeline' => true, + 'loading' => 'defer', + 'group' => 'head' + ], reset($array)); + } + + public function testAddingAssetPropertiesWithArray() + { + //Test adding assets with object to define properties + $this->assets->reset(); + $this->assets->addJs('test.js', ['loading' => 'async']); + $js = $this->assets->js(); + $this->assertSame('' . PHP_EOL, $js); + $this->assets->reset(); + + } + + public function testAddingJSAssetPropertiesWithArrayFromCollection() + { + //Test adding properties with array + $this->assets->reset(); + $this->assets->addJs('jquery', ['loading' => 'async']); + $js = $this->assets->js(); + $this->assertSame('' . PHP_EOL, $js); + + //Test priority too + $this->assets->reset(); + $this->assets->addJs('jquery', ['loading' => 'async', 'priority' => 1]); + $this->assets->addJs('test.js', ['loading' => 'async', 'priority' => 2]); + $js = $this->assets->js(); + $this->assertSame('' . PHP_EOL . + '' . PHP_EOL, $js); + + //Test multiple groups + $this->assets->reset(); + $this->assets->addJs('jquery', ['loading' => 'async', 'priority' => 1, 'group' => 'footer']); + $this->assets->addJs('test.js', ['loading' => 'async', 'priority' => 2]); + $js = $this->assets->js(); + $this->assertSame('' . PHP_EOL, $js); + $js = $this->assets->js('footer'); + $this->assertSame('' . PHP_EOL, $js); + + //Test adding array of assets + //Test priority too + $this->assets->reset(); + $this->assets->addJs(['jquery', 'test.js'], ['loading' => 'async']); + $js = $this->assets->js(); + + $this->assertSame('' . PHP_EOL . + '' . PHP_EOL, $js); + } + + public function testAddingCSSAssetPropertiesWithArrayFromCollection() + { + $this->assets->registerCollection('test', ['/system/assets/whoops.css']); + + //Test priority too + $this->assets->reset(); + $this->assets->addCss('test', ['priority' => 1]); + $this->assets->addCss('test.css', ['priority' => 2]); + $css = $this->assets->css(); + $this->assertSame('' . PHP_EOL . + '' . PHP_EOL, $css); + + //Test multiple groups + $this->assets->reset(); + $this->assets->addCss('test', ['priority' => 1, 'group' => 'footer']); + $this->assets->addCss('test.css', ['priority' => 2]); + $css = $this->assets->css(); + $this->assertSame('' . PHP_EOL, $css); + $css = $this->assets->css('footer'); + $this->assertSame('' . PHP_EOL, $css); + + //Test adding array of assets + //Test priority too + $this->assets->reset(); + $this->assets->addCss(['test', 'test.css'], ['loading' => 'async']); + $css = $this->assets->css(); + $this->assertSame('' . PHP_EOL . + '' . PHP_EOL, $css); + } + + public function testPriorityOfAssets() + { + $this->assets->reset(); + $this->assets->add('test.css'); + $this->assets->add('test-after.css'); + + $css = $this->assets->css(); + $this->assertSame('' . PHP_EOL . + '' . PHP_EOL, $css); + + //---------------- + $this->assets->reset(); + $this->assets->add('test-after.css', 1); + $this->assets->add('test.css', 2); + + $css = $this->assets->css(); + $this->assertSame('' . PHP_EOL . + '' . PHP_EOL, $css); + + //---------------- + $this->assets->reset(); + $this->assets->add('test-after.css', 1); + $this->assets->add('test.css', 2); + $this->assets->add('test-before.css', 3); + + $css = $this->assets->css(); + $this->assertSame('' . PHP_EOL . + '' . PHP_EOL . + '' . PHP_EOL, $css); + } + + public function testPipeline() + { + $this->assets->reset(); + + //File not existing. Pipeline searches for that file without reaching it. Output is empty. + $this->assets->add('test.css', null, true); + $this->assets->setCssPipeline(true); + $css = $this->assets->css(); + $this->assertSame('', $css); + + //Add a core Grav CSS file, which is found. Pipeline will now return a file + $this->assets->add('/system/assets/debugger.css', null, true); + $css = $this->assets->css(); + $this->assertContains('', $css); + } + + public function testAddAsyncJs() + { + $this->assets->reset(); + $this->assets->addAsyncJs('jquery'); + $js = $this->assets->js(); + $this->assertSame('' . PHP_EOL, $js); + } + + public function testAddDeferJs() + { + $this->assets->reset(); + $this->assets->addDeferJs('jquery'); + $js = $this->assets->js(); + $this->assertSame('' . PHP_EOL, $js); + } + + public function testAddInlineCss() + { + $this->assets->reset(); + $this->assets->addInlineCss('body { color: black }'); + $css = $this->assets->css(); + $this->assertSame(PHP_EOL . '' . PHP_EOL, $css); + } + + public function testAddInlineJs() + { + $this->assets->reset(); + $this->assets->addInlineJs('alert("test")'); + $js = $this->assets->js(); + $this->assertSame(PHP_EOL . '' . PHP_EOL, $js); + } + + public function testGetCollections() + { + $this->assertTrue(is_array($this->assets->getCollections())); + $this->assertTrue(in_array('jquery', array_keys($this->assets->getCollections()))); + $this->assertTrue(in_array('system://assets/jquery/jquery-2.x.min.js', $this->assets->getCollections())); + } + + public function testExists() + { + $this->assertTrue($this->assets->exists('jquery')); + $this->assertFalse($this->assets->exists('another-unexisting-library')); + } + + public function testRegisterCollection() + { + $this->assets->registerCollection('debugger', ['/system/assets/debugger.css']); + $this->assertTrue($this->assets->exists('debugger')); + $this->assertTrue(in_array('debugger', array_keys($this->assets->getCollections()))); + } + + public function testReset() + { + $this->assets->addInlineJs('alert("test")'); + $this->assets->reset(); + $this->assertTrue(count($this->assets->js()) == 0); + + $this->assets->addAsyncJs('jquery'); + $this->assets->reset(); + + $this->assertTrue(count($this->assets->js()) == 0); + + $this->assets->addInlineCss('body { color: black }'); + $this->assets->reset(); + + $this->assertTrue(count($this->assets->css()) == 0); + + $this->assets->add('/system/assets/debugger.css', null, true); + $this->assets->reset(); + + $this->assertTrue(count($this->assets->css()) == 0); + } + + public function testResetJs() + { + $this->assets->addInlineJs('alert("test")'); + $this->assets->resetJs(); + $this->assertTrue(count($this->assets->js()) == 0); + + $this->assets->addAsyncJs('jquery'); + $this->assets->resetJs(); + + $this->assertTrue(count($this->assets->js()) == 0); + } + + public function testResetCss() + { + $this->assertTrue(count($this->assets->js()) == 0); + + $this->assets->addInlineCss('body { color: black }'); + $this->assets->resetCss(); + + $this->assertTrue(count($this->assets->css()) == 0); + + $this->assets->add('/system/assets/debugger.css', null, true); + $this->assets->resetCss(); + + $this->assertTrue(count($this->assets->css()) == 0); + } + + public function testAddDirCss() + { + $this->assets->addDirCss('/system'); + + $this->assertTrue(is_array($this->assets->getCss())); + $this->assertTrue(count($this->assets->getCss()) > 0); + $this->assertTrue(is_array($this->assets->getJs())); + $this->assertTrue(count($this->assets->getJs()) == 0); + + $this->assets->reset(); + $this->assets->addDirCss('/system/assets'); + + $this->assertTrue(is_array($this->assets->getCss())); + $this->assertTrue(count($this->assets->getCss()) > 0); + $this->assertTrue(is_array($this->assets->getJs())); + $this->assertTrue(count($this->assets->getJs()) == 0); + + $this->assets->reset(); + $this->assets->addDirJs('/system'); + + $this->assertTrue(is_array($this->assets->getCss())); + $this->assertTrue(count($this->assets->getCss()) == 0); + $this->assertTrue(is_array($this->assets->getJs())); + $this->assertTrue(count($this->assets->getJs()) > 0); + + $this->assets->reset(); + $this->assets->addDirJs('/system/assets'); + + $this->assertTrue(is_array($this->assets->getCss())); + $this->assertTrue(count($this->assets->getCss()) == 0); + $this->assertTrue(is_array($this->assets->getJs())); + $this->assertTrue(count($this->assets->getJs()) > 0); + + $this->assets->reset(); + $this->assets->addDir('/system/assets'); + + $this->assertTrue(is_array($this->assets->getCss())); + $this->assertTrue(count($this->assets->getCss()) > 0); + $this->assertTrue(is_array($this->assets->getJs())); + $this->assertTrue(count($this->assets->getJs()) > 0); + + //Use streams + $this->assets->reset(); + $this->assets->addDir('system://assets'); + + $this->assertTrue(is_array($this->assets->getCss())); + $this->assertTrue(count($this->assets->getCss()) > 0); + $this->assertTrue(is_array($this->assets->getJs())); + $this->assertTrue(count($this->assets->getJs()) > 0); + + } +} diff --git a/tests/unit/Grav/Common/BrowserTest.php b/tests/unit/Grav/Common/BrowserTest.php new file mode 100644 index 0000000..e5aca99 --- /dev/null +++ b/tests/unit/Grav/Common/BrowserTest.php @@ -0,0 +1,47 @@ +grav = Fixtures::get('grav'); + } + + protected function _after() + { + } + + public function testGetBrowser() + { /* Already covered by PhpUserAgent tests */ + } + + public function testGetPlatform() + { /* Already covered by PhpUserAgent tests */ + } + + public function testGetLongVersion() + { /* Already covered by PhpUserAgent tests */ + } + + public function testGetVersion() + { /* Already covered by PhpUserAgent tests */ + } + + public function testIsHuman() + { + //Already Partially covered by PhpUserAgent tests + + //Make sure it recognizes the test as not human + $this->assertFalse($this->grav['browser']->isHuman()); + } +} + diff --git a/tests/unit/Grav/Common/ComposerTest.php b/tests/unit/Grav/Common/ComposerTest.php new file mode 100644 index 0000000..da64b66 --- /dev/null +++ b/tests/unit/Grav/Common/ComposerTest.php @@ -0,0 +1,33 @@ +assertTrue(is_string($composerLocation)); + $this->assertTrue($composerLocation[0] == '/'); + } + + public function testGetComposerExecutor() + { + $composerExecutor = Composer::getComposerExecutor(); + $this->assertTrue(is_string($composerExecutor)); + $this->assertTrue($composerExecutor[0] == '/'); + $this->assertTrue(strstr($composerExecutor, 'php') !== null); + $this->assertTrue(strstr($composerExecutor, 'composer') !== null); + } + +} + diff --git a/tests/unit/Grav/Common/Markdown/ParsedownTest.php b/tests/unit/Grav/Common/Markdown/ParsedownTest.php new file mode 100644 index 0000000..fd3d4e3 --- /dev/null +++ b/tests/unit/Grav/Common/Markdown/ParsedownTest.php @@ -0,0 +1,715 @@ +grav = Fixtures::get('grav'); + $this->pages = $this->grav['pages']; + $this->config = $this->grav['config']; + $this->uri = $this->grav['uri']; + $this->language = $this->grav['language']; + $this->old_home = $this->config->get('system.home.alias'); + $this->config->set('system.home.alias', '/item1'); + $this->config->set('system.absolute_urls', false); + $this->config->set('system.languages.supported', []); + + unset($this->grav['language']); + $this->grav['language'] = new Language($this->grav); + + if (!self::$run) { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + $locator->addPath('page', '', 'tests/fake/nested-site/user/pages', false); + $this->pages->init(); + self::$run = true; + } + + $defaults = [ + 'extra' => false, + 'auto_line_breaks' => false, + 'auto_url_links' => false, + 'escape_markup' => false, + 'special_chars' => ['>' => 'gt', '<' => 'lt'], + ]; + $page = $this->pages->dispatch('/item2/item2-2'); + $this->parsedown = new Parsedown($page, $defaults); + } + + protected function _after() + { + $this->config->set('system.home.alias', $this->old_home); + } + + public function testImages() + { + $this->uri->initializeWithURL('http://testing.dev/item2/item2-2')->init(); + + $this->assertSame('

', + $this->parsedown->text('![](sample-image.jpg)')); + $this->assertRegexp('|

<\/p>|', + $this->parsedown->text('![](cache-image.jpg?cropResize=200,200&foo)')); + $this->assertRegexp('|

<\/p>|', + $this->parsedown->text('![](/home-cache-image.jpg?cache)')); + $this->assertSame('

', + $this->parsedown->text('![](missing-image.jpg)')); + $this->assertSame('

', + $this->parsedown->text('![](/home-missing-image.jpg)')); + + $this->config->set('system.languages.supported', ['fr','en']); + unset($this->grav['language']); + $this->grav['language'] = new Language($this->grav); + $this->uri->initializeWithURL('http://testing.dev/fr/item2/item2-2')->init(); + + $this->assertSame('

', + $this->parsedown->text('![](sample-image.jpg)')); + $this->assertRegexp('|

<\/p>|', + $this->parsedown->text('![](cache-image.jpg?cropResize=200,200&foo)')); + } + + public function testImagesSubDir() + { + $this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/item2/item2-2', '/subdir')->init(); + + $this->assertRegexp('|

<\/p>|', + $this->parsedown->text('![](/home-cache-image.jpg?cache)')); + $this->assertSame('

', + $this->parsedown->text('![](sample-image.jpg)')); + $this->assertRegexp('|

<\/p>|', + $this->parsedown->text('![](cache-image.jpg?cache)')); + $this->assertSame('

', + $this->parsedown->text('![](missing-image.jpg)')); + $this->assertSame('

', + $this->parsedown->text('![](/home-missing-image.jpg)')); + + } + + public function testImagesAbsoluteUrls() + { + $this->config->set('system.absolute_urls', true); + $this->uri->initializeWithURL('http://testing.dev/item2/item2-2')->init(); + + $this->assertSame('

', + $this->parsedown->text('![](sample-image.jpg)')); + $this->assertRegexp('|

<\/p>|', + $this->parsedown->text('![](cache-image.jpg?cache)')); + $this->assertRegexp('|

<\/p>|', + $this->parsedown->text('![](/home-cache-image.jpg?cache)')); + $this->assertSame('

', + $this->parsedown->text('![](missing-image.jpg)')); + $this->assertSame('

', + $this->parsedown->text('![](/home-missing-image.jpg)')); + } + + public function testImagesSubDirAbsoluteUrls() + { + $this->config->set('system.absolute_urls', true); + $this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/item2/item2-2', '/subdir')->init(); + + $this->assertSame('

', + $this->parsedown->text('![](sample-image.jpg)')); + $this->assertRegexp('|

<\/p>|', + $this->parsedown->text('![](cache-image.jpg?cache)')); + $this->assertRegexp('|

<\/p>|', + $this->parsedown->text('![](/home-cache-image.jpg?cropResize=200,200)')); + $this->assertSame('

', + $this->parsedown->text('![](missing-image.jpg)')); + $this->assertSame('

', + $this->parsedown->text('![](/home-missing-image.jpg)')); + } + + public function testRootImages() + { + $this->uri->initializeWithURL('http://testing.dev/')->init(); + + $defaults = [ + 'extra' => false, + 'auto_line_breaks' => false, + 'auto_url_links' => false, + 'escape_markup' => false, + 'special_chars' => ['>' => 'gt', '<' => 'lt'], + ]; + $page = $this->pages->dispatch('/'); + $this->parsedown = new Parsedown($page, $defaults); + + $this->assertSame('

', + $this->parsedown->text('![](home-sample-image.jpg)')); + $this->assertRegexp('|

<\/p>|', + $this->parsedown->text('![](home-cache-image.jpg?cache)')); + $this->assertRegexp('|

<\/p>|', + $this->parsedown->text('![](home-cache-image.jpg?cropResize=200,200&foo)')); + $this->assertSame('

', + $this->parsedown->text('![](/home-missing-image.jpg)')); + + $this->config->set('system.languages.supported', ['fr','en']); + unset($this->grav['language']); + $this->grav['language'] = new Language($this->grav); + $this->uri->initializeWithURL('http://testing.dev/fr/item2/item2-2')->init(); + + $this->assertSame('

', + $this->parsedown->text('![](home-sample-image.jpg)')); + + + } + + public function testRootImagesSubDirAbsoluteUrls() + { + $this->config->set('system.absolute_urls', true); + $this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/item2/item2-2', '/subdir')->init(); + + $this->assertSame('

', + $this->parsedown->text('![](sample-image.jpg)')); + $this->assertRegexp('|

<\/p>|', + $this->parsedown->text('![](cache-image.jpg?cache)')); + $this->assertRegexp('|

<\/p>|', + $this->parsedown->text('![](/home-cache-image.jpg?cropResize=200,200)')); + $this->assertSame('

', + $this->parsedown->text('![](missing-image.jpg)')); + $this->assertSame('

', + $this->parsedown->text('![](/home-missing-image.jpg)')); + } + + public function testRootAbsoluteLinks() + { + $this->uri->initializeWithURL('http://testing.dev/')->init(); + + $defaults = [ + 'extra' => false, + 'auto_line_breaks' => false, + 'auto_url_links' => false, + 'escape_markup' => false, + 'special_chars' => ['>' => 'gt', '<' => 'lt'], + ]; + $page = $this->pages->dispatch('/'); + $this->parsedown = new Parsedown($page, $defaults); + + + $this->assertSame('

Down a Level

', + $this->parsedown->text('[Down a Level](item1-3)')); + + $this->assertSame('

Peer Page

', + $this->parsedown->text('[Peer Page](../item2)')); + + $this->assertSame('

With Query

', + $this->parsedown->text('[With Query](?foo=bar)')); + $this->assertSame('

With Param

', + $this->parsedown->text('[With Param](/foo:bar)')); + $this->assertSame('

With Anchor

', + $this->parsedown->text('[With Anchor](#foo)')); + + $this->config->set('system.languages.supported', ['fr','en']); + unset($this->grav['language']); + $this->grav['language'] = new Language($this->grav); + $this->uri->initializeWithURL('http://testing.dev/fr/item2/item2-2')->init(); + + $this->assertSame('

Peer Page

', + $this->parsedown->text('[Peer Page](../item2)')); + $this->assertSame('

Down a Level

', + $this->parsedown->text('[Down a Level](item1-3)')); + $this->assertSame('

With Query

', + $this->parsedown->text('[With Query](?foo=bar)')); + $this->assertSame('

With Param

', + $this->parsedown->text('[With Param](/foo:bar)')); + $this->assertSame('

With Anchor

', + $this->parsedown->text('[With Anchor](#foo)')); + } + + + public function testAnchorLinksLangRelativeUrls() + { + $this->config->set('system.languages.supported', ['fr','en']); + unset($this->grav['language']); + $this->grav['language'] = new Language($this->grav); + $this->uri->initializeWithURL('http://testing.dev/fr/item2/item2-2')->init(); + + $this->assertSame('

Current Anchor

', + $this->parsedown->text('[Current Anchor](#foo)')); + $this->assertSame('

Root Anchor

', + $this->parsedown->text('[Root Anchor](/#foo)')); + $this->assertSame('

Peer Anchor

', + $this->parsedown->text('[Peer Anchor](../item2-1#foo)')); + $this->assertSame('

Peer Anchor 2

', + $this->parsedown->text('[Peer Anchor 2](../item2-1/#foo)')); + + } + + public function testAnchorLinksLangAbsoluteUrls() + { + $this->config->set('system.absolute_urls', true); + $this->config->set('system.languages.supported', ['fr','en']); + unset($this->grav['language']); + $this->grav['language'] = new Language($this->grav); + $this->uri->initializeWithURL('http://testing.dev/fr/item2/item2-2')->init(); + + $this->assertSame('

Peer Anchor

', + $this->parsedown->text('[Peer Anchor](../item2-1#foo)')); + $this->assertSame('

Peer Anchor 2

', + $this->parsedown->text('[Peer Anchor 2](../item2-1/#foo)')); + $this->assertSame('

Current Anchor

', + $this->parsedown->text('[Current Anchor](#foo)')); + $this->assertSame('

Root Anchor

', + $this->parsedown->text('[Root Anchor](/#foo)')); + + } + + + public function testExternalLinks() + { + $this->assertSame('

cnn.com

', + $this->parsedown->text('[cnn.com](http://www.cnn.com)')); + $this->assertSame('

google.com

', + $this->parsedown->text('[google.com](https://www.google.com)')); + } + + public function testExternalLinksSubDir() + { + $this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/item2/item2-2', '/subdir')->init(); + + $this->assertSame('

cnn.com

', + $this->parsedown->text('[cnn.com](http://www.cnn.com)')); + $this->assertSame('

google.com

', + $this->parsedown->text('[google.com](https://www.google.com)')); + } + + public function testExternalLinksSubDirAbsoluteUrls() + { + $this->config->set('system.absolute_urls', true); + $this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/item2/item2-2', '/subdir')->init(); + + $this->assertSame('

cnn.com

', + $this->parsedown->text('[cnn.com](http://www.cnn.com)')); + $this->assertSame('

google.com

', + $this->parsedown->text('[google.com](https://www.google.com)')); + } + + public function testAnchorLinksRelativeUrls() + { + $this->uri->initializeWithURL('http://testing.dev/item2/item2-2')->init(); + + $this->assertSame('

Current Anchor

', + $this->parsedown->text('[Current Anchor](#foo)')); + $this->assertSame('

Root Anchor

', + $this->parsedown->text('[Root Anchor](/#foo)')); + $this->assertSame('

Peer Anchor

', + $this->parsedown->text('[Peer Anchor](../item2-1#foo)')); + $this->assertSame('

Peer Anchor 2

', + $this->parsedown->text('[Peer Anchor 2](../item2-1/#foo)')); + } + + public function testAnchorLinksAbsoluteUrls() + { + $this->config->set('system.absolute_urls', true); + $this->uri->initializeWithURL('http://testing.dev/item2/item2-2')->init(); + + $this->assertSame('

Peer Anchor

', + $this->parsedown->text('[Peer Anchor](../item2-1#foo)')); + $this->assertSame('

Peer Anchor 2

', + $this->parsedown->text('[Peer Anchor 2](../item2-1/#foo)')); + $this->assertSame('

Current Anchor

', + $this->parsedown->text('[Current Anchor](#foo)')); + $this->assertSame('

Root Anchor

', + $this->parsedown->text('[Root Anchor](/#foo)')); + } + + + + + public function testAnchorLinksWithPortAbsoluteUrls() + { + $this->config->set('system.absolute_urls', true); + $this->uri->initializeWithURL('http://testing.dev:8080/item2/item2-2')->init(); + + $this->assertSame('

Peer Anchor

', + $this->parsedown->text('[Peer Anchor](../item2-1#foo)')); + $this->assertSame('

Peer Anchor 2

', + $this->parsedown->text('[Peer Anchor 2](../item2-1/#foo)')); + $this->assertSame('

Current Anchor

', + $this->parsedown->text('[Current Anchor](#foo)')); + $this->assertSame('

Root Anchor

', + $this->parsedown->text('[Root Anchor](/#foo)')); + } + + public function testAnchorLinksSubDirRelativeUrls() + { + $this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/item2/item2-2', '/subdir')->init(); + + $this->assertSame('

Peer Anchor

', + $this->parsedown->text('[Peer Anchor](../item2-1#foo)')); + $this->assertSame('

Peer Anchor 2

', + $this->parsedown->text('[Peer Anchor 2](../item2-1/#foo)')); + $this->assertSame('

Current Anchor

', + $this->parsedown->text('[Current Anchor](#foo)')); + $this->assertSame('

Root Anchor

', + $this->parsedown->text('[Root Anchor](/#foo)')); + } + + public function testAnchorLinksSubDirAbsoluteUrls() + { + $this->config->set('system.absolute_urls', true); + $this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/item2/item2-2', '/subdir')->init(); + + $this->assertSame('

Peer Anchor

', + $this->parsedown->text('[Peer Anchor](../item2-1#foo)')); + $this->assertSame('

Peer Anchor 2

', + $this->parsedown->text('[Peer Anchor 2](../item2-1/#foo)')); + $this->assertSame('

Current Anchor

', + $this->parsedown->text('[Current Anchor](#foo)')); + $this->assertSame('

Root Anchor

', + $this->parsedown->text('[Root Anchor](/#foo)')); + } + + public function testSlugRelativeLinks() + { + $this->uri->initializeWithURL('http://testing.dev/item2/item2-2')->init(); + + $this->assertSame('

Up to Root Level

', + $this->parsedown->text('[Up to Root Level](../..)')); + $this->assertSame('

Peer Page

', + $this->parsedown->text('[Peer Page](../item2-1)')); + $this->assertSame('

Down a Level

', + $this->parsedown->text('[Down a Level](item2-2-1)')); + $this->assertSame('

Up a Level

', + $this->parsedown->text('[Up a Level](..)')); + $this->assertSame('

Up and Down

', + $this->parsedown->text('[Up and Down](../../item3/item3-3)')); + $this->assertSame('

Down a Level with Query

', + $this->parsedown->text('[Down a Level with Query](item2-2-1?foo=bar)')); + $this->assertSame('

Up a Level with Query

', + $this->parsedown->text('[Up a Level with Query](../?foo=bar)')); + $this->assertSame('

Up and Down with Query

', + $this->parsedown->text('[Up and Down with Query](../../item3/item3-3?foo=bar)')); + $this->assertSame('

Up and Down with Param

', + $this->parsedown->text('[Up and Down with Param](../../item3/item3-3/foo:bar)')); + $this->assertSame('

Up and Down with Anchor

', + $this->parsedown->text('[Up and Down with Anchor](../../item3/item3-3#foo)')); + } + + public function testSlugRelativeLinksAbsoluteUrls() + { + $this->config->set('system.absolute_urls', true); + $this->uri->initializeWithURL('http://testing.dev/item2/item2-2')->init(); + + $this->assertSame('

Peer Page

', + $this->parsedown->text('[Peer Page](../item2-1)')); + $this->assertSame('

Down a Level

', + $this->parsedown->text('[Down a Level](item2-2-1)')); + $this->assertSame('

Up a Level

', + $this->parsedown->text('[Up a Level](..)')); + $this->assertSame('

Up to Root Level

', + $this->parsedown->text('[Up to Root Level](../..)')); + $this->assertSame('

Up and Down

', + $this->parsedown->text('[Up and Down](../../item3/item3-3)')); + $this->assertSame('

Down a Level with Query

', + $this->parsedown->text('[Down a Level with Query](item2-2-1?foo=bar)')); + $this->assertSame('

Up a Level with Query

', + $this->parsedown->text('[Up a Level with Query](../?foo=bar)')); + $this->assertSame('

Up and Down with Query

', + $this->parsedown->text('[Up and Down with Query](../../item3/item3-3?foo=bar)')); + $this->assertSame('

Up and Down with Param

', + $this->parsedown->text('[Up and Down with Param](../../item3/item3-3/foo:bar)')); + $this->assertSame('

Up and Down with Anchor

', + $this->parsedown->text('[Up and Down with Anchor](../../item3/item3-3#foo)')); + } + + public function testSlugRelativeLinksSubDir() + { + $this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/item2/item2-2', '/subdir')->init(); + + $this->assertSame('

Peer Page

', + $this->parsedown->text('[Peer Page](../item2-1)')); + $this->assertSame('

Down a Level

', + $this->parsedown->text('[Down a Level](item2-2-1)')); + $this->assertSame('

Up a Level

', + $this->parsedown->text('[Up a Level](..)')); + $this->assertSame('

Up to Root Level

', + $this->parsedown->text('[Up to Root Level](../..)')); + $this->assertSame('

Up and Down

', + $this->parsedown->text('[Up and Down](../../item3/item3-3)')); + $this->assertSame('

Down a Level with Query

', + $this->parsedown->text('[Down a Level with Query](item2-2-1?foo=bar)')); + $this->assertSame('

Up a Level with Query

', + $this->parsedown->text('[Up a Level with Query](../?foo=bar)')); + $this->assertSame('

Up and Down with Query

', + $this->parsedown->text('[Up and Down with Query](../../item3/item3-3?foo=bar)')); + $this->assertSame('

Up and Down with Param

', + $this->parsedown->text('[Up and Down with Param](../../item3/item3-3/foo:bar)')); + $this->assertSame('

Up and Down with Anchor

', + $this->parsedown->text('[Up and Down with Anchor](../../item3/item3-3#foo)')); + } + + public function testSlugRelativeLinksSubDirAbsoluteUrls() + { + $this->config->set('system.absolute_urls', true); + $this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/item2/item2-2', '/subdir')->init(); + + $this->assertSame('

Peer Page

', + $this->parsedown->text('[Peer Page](../item2-1)')); + $this->assertSame('

Down a Level

', + $this->parsedown->text('[Down a Level](item2-2-1)')); + $this->assertSame('

Up a Level

', + $this->parsedown->text('[Up a Level](..)')); + $this->assertSame('

Up to Root Level

', + $this->parsedown->text('[Up to Root Level](../..)')); + $this->assertSame('

Up and Down

', + $this->parsedown->text('[Up and Down](../../item3/item3-3)')); + $this->assertSame('

Down a Level with Query

', + $this->parsedown->text('[Down a Level with Query](item2-2-1?foo=bar)')); + $this->assertSame('

Up a Level with Query

', + $this->parsedown->text('[Up a Level with Query](../?foo=bar)')); + $this->assertSame('

Up and Down with Query

', + $this->parsedown->text('[Up and Down with Query](../../item3/item3-3?foo=bar)')); + $this->assertSame('

Up and Down with Param

', + $this->parsedown->text('[Up and Down with Param](../../item3/item3-3/foo:bar)')); + $this->assertSame('

Up and Down with Anchor

', + $this->parsedown->text('[Up and Down with Anchor](../../item3/item3-3#foo)')); + } + + + public function testDirectoryRelativeLinks() + { + $this->uri->initializeWithURL('http://testing.dev/item2/item2-2')->init(); + + $this->assertSame('

Up and Down with Param

', + $this->parsedown->text('[Up and Down with Param](../../03.item3/03.item3-3/foo:bar)')); + $this->assertSame('

Peer Page

', + $this->parsedown->text('[Peer Page](../01.item2-1)')); + $this->assertSame('

Down a Level

', + $this->parsedown->text('[Down a Level](01.item2-2-1)')); + $this->assertSame('

Up and Down

', + $this->parsedown->text('[Up and Down](../../03.item3/03.item3-3)')); + $this->assertSame('

Down a Level with Query

', + $this->parsedown->text('[Down a Level with Query](01.item2-2-1?foo=bar)')); + $this->assertSame('

Up and Down with Query

', + $this->parsedown->text('[Up and Down with Query](../../03.item3/03.item3-3?foo=bar)')); + $this->assertSame('

Up and Down with Anchor

', + $this->parsedown->text('[Up and Down with Anchor](../../03.item3/03.item3-3#foo)')); + } + + + public function testAbsoluteLinks() + { + $this->uri->initializeWithURL('http://testing.dev/item2/item2-2')->init(); + + $this->assertSame('

Root

', + $this->parsedown->text('[Root](/)')); + $this->assertSame('

Peer Page

', + $this->parsedown->text('[Peer Page](/item2/item2-1)')); + $this->assertSame('

Down a Level

', + $this->parsedown->text('[Down a Level](/item2/item2-2/item2-2-1)')); + $this->assertSame('

Up a Level

', + $this->parsedown->text('[Up a Level](/item2)')); + $this->assertSame('

With Query

', + $this->parsedown->text('[With Query](/item2?foo=bar)')); + $this->assertSame('

With Param

', + $this->parsedown->text('[With Param](/item2/foo:bar)')); + $this->assertSame('

With Anchor

', + $this->parsedown->text('[With Anchor](/item2#foo)')); + } + + public function testDirectoryAbsoluteLinksSubDir() + { + $this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/item2/item2-2', '/subdir')->init(); + + $this->assertSame('

Root

', + $this->parsedown->text('[Root](/)')); + $this->assertSame('

Peer Page

', + $this->parsedown->text('[Peer Page](/item2/item2-1)')); + $this->assertSame('

Down a Level

', + $this->parsedown->text('[Down a Level](/item2/item2-2/item2-2-1)')); + $this->assertSame('

Up a Level

', + $this->parsedown->text('[Up a Level](/item2)')); + $this->assertSame('

With Query

', + $this->parsedown->text('[With Query](/item2?foo=bar)')); + $this->assertSame('

With Param

', + $this->parsedown->text('[With Param](/item2/foo:bar)')); + $this->assertSame('

With Anchor

', + $this->parsedown->text('[With Anchor](/item2#foo)')); + } + + public function testDirectoryAbsoluteLinksSubDirAbsoluteUrl() + { + $this->config->set('system.absolute_urls', true); + $this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/item2/item2-2', '/subdir')->init(); + + $this->assertSame('

Root

', + $this->parsedown->text('[Root](/)')); + $this->assertSame('

Peer Page

', + $this->parsedown->text('[Peer Page](/item2/item2-1)')); + $this->assertSame('

Down a Level

', + $this->parsedown->text('[Down a Level](/item2/item2-2/item2-2-1)')); + $this->assertSame('

Up a Level

', + $this->parsedown->text('[Up a Level](/item2)')); + $this->assertSame('

With Query

', + $this->parsedown->text('[With Query](/item2?foo=bar)')); + $this->assertSame('

With Param

', + $this->parsedown->text('[With Param](/item2/foo:bar)')); + $this->assertSame('

With Anchor

', + $this->parsedown->text('[With Anchor](/item2#foo)')); + } + + public function testSpecialProtocols() + { + $this->uri->initializeWithURL('http://testing.dev/item2/item2-2')->init(); + + $this->assertSame('

mailto

', + $this->parsedown->text('[mailto](mailto:user@domain.com)')); + $this->assertSame('

xmpp

', + $this->parsedown->text('[xmpp](xmpp:xyx@domain.com)')); + $this->assertSame('

tel

', + $this->parsedown->text('[tel](tel:123-555-12345)')); + $this->assertSame('

sms

', + $this->parsedown->text('[sms](sms:123-555-12345)')); + } + + public function testSpecialProtocolsSubDir() + { + $this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/item2/item2-2', '/subdir')->init(); + + $this->assertSame('

mailto

', + $this->parsedown->text('[mailto](mailto:user@domain.com)')); + $this->assertSame('

xmpp

', + $this->parsedown->text('[xmpp](xmpp:xyx@domain.com)')); + $this->assertSame('

tel

', + $this->parsedown->text('[tel](tel:123-555-12345)')); + $this->assertSame('

sms

', + $this->parsedown->text('[sms](sms:123-555-12345)')); + } + + public function testSpecialProtocolsSubDirAbsoluteUrl() + { + $this->config->set('system.absolute_urls', true); + $this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/item2/item2-2', '/subdir')->init(); + + $this->assertSame('

mailto

', + $this->parsedown->text('[mailto](mailto:user@domain.com)')); + $this->assertSame('

xmpp

', + $this->parsedown->text('[xmpp](xmpp:xyx@domain.com)')); + $this->assertSame('

tel

', + $this->parsedown->text('[tel](tel:123-555-12345)')); + $this->assertSame('

sms

', + $this->parsedown->text('[sms](sms:123-555-12345)')); + } + + public function testReferenceLinks() + { + $this->uri->initializeWithURL('http://testing.dev/item2/item2-2')->init(); + + $sample = '[relative link][r_relative] + [r_relative]: ../item2-3#blah'; + $this->assertSame('

relative link

', + $this->parsedown->text($sample)); + + $sample = '[absolute link][r_absolute] + [r_absolute]: /item3#blah'; + $this->assertSame('

absolute link

', + $this->parsedown->text($sample)); + + $sample = '[external link][r_external] + [r_external]: http://www.cnn.com'; + $this->assertSame('

external link

', + $this->parsedown->text($sample)); + } + + public function testAttributeLinks() + { + $this->uri->initializeWithURL('http://testing.dev/item2/item2-2')->init(); + + $this->assertSame('

Relative Class

', + $this->parsedown->text('[Relative Class](../item2-3?classes=button)')); + $this->assertSame('

Relative ID

', + $this->parsedown->text('[Relative ID](../item2-3?id=unique)')); + $this->assertSame('

External

', + $this->parsedown->text('[External](https://github.com/getgrav/grav?classes=button,big)')); + $this->assertSame('

Relative Noprocess

', + $this->parsedown->text('[Relative Noprocess](../item2-3?id=unique&noprocess)')); + $this->assertSame('

Relative Target

', + $this->parsedown->text('[Relative Target](../item2-3?target=_blank)')); + $this->assertSame('

Relative Rel

', + $this->parsedown->text('[Relative Rel](../item2-3?rel=nofollow)')); + $this->assertSame('

Relative Mixed

', + $this->parsedown->text('[Relative Mixed](../item2-3?foo=bar&baz=qux&rel=nofollow&class=button)')); + } + + public function testInvalidLinks() + { + $this->uri->initializeWithURL('http://testing.dev/item2/item2-2')->init(); + + $this->assertSame('

Non Existent Page

', + $this->parsedown->text('[Non Existent Page](no-page)')); + $this->assertSame('

Existent File

', + $this->parsedown->text('[Existent File](existing-file.zip)')); + $this->assertSame('

Non Existent File

', + $this->parsedown->text('[Non Existent File](missing-file.zip)')); + } + + public function testInvalidLinksSubDir() + { + $this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/item2/item2-2', '/subdir')->init(); + + $this->assertSame('

Non Existent Page

', + $this->parsedown->text('[Non Existent Page](no-page)')); + $this->assertSame('

Existent File

', + $this->parsedown->text('[Existent File](existing-file.zip)')); + $this->assertSame('

Non Existent File

', + $this->parsedown->text('[Non Existent File](missing-file.zip)')); + } + + public function testInvalidLinksSubDirAbsoluteUrl() + { + $this->config->set('system.absolute_urls', true); + $this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/item2/item2-2', '/subdir')->init(); + + $this->assertSame('

Non Existent Page

', + $this->parsedown->text('[Non Existent Page](no-page)')); + $this->assertSame('

Existent File

', + $this->parsedown->text('[Existent File](existing-file.zip)')); + $this->assertSame('

Non Existent File

', + $this->parsedown->text('[Non Existent File](missing-file.zip)')); + } + + + /** + * @param $string + * + * @return mixed + */ + private function stripLeadingWhitespace($string) + { + return preg_replace('/^\s*(.*)/', '', $string); + } + +} diff --git a/tests/unit/Grav/Common/Page/PagesTest.php b/tests/unit/Grav/Common/Page/PagesTest.php new file mode 100644 index 0000000..c265567 --- /dev/null +++ b/tests/unit/Grav/Common/Page/PagesTest.php @@ -0,0 +1,286 @@ +grav = Fixtures::get('grav'); + $this->pages = $this->grav['pages']; + $this->grav['config']->set('system.home.alias', '/home'); + + if (!self::$run) { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + + $locator->addPath('page', '', 'tests/fake/simple-site/user/pages', false); + $this->pages->init(); + self::$run = true; + } + + } + + public function testBase() + { + $this->assertSame('', $this->pages->base()); + $this->pages->base('/test'); + $this->assertSame('/test', $this->pages->base()); + $this->pages->base(''); + $this->assertSame(null, $this->pages->base()); + } + + public function testLastModified() + { + $this->assertSame(null, $this->pages->lastModified()); + $this->pages->lastModified('test'); + $this->assertSame('test', $this->pages->lastModified()); + } + + public function testInstances() + { + $this->assertTrue(is_array($this->pages->instances())); + foreach($this->pages->instances() as $instance) { + $this->assertInstanceOf('Grav\Common\Page\Page', $instance); + } + } + + public function testRoutes() + { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + + $this->assertTrue(is_array($this->pages->routes())); + $this->assertSame($locator->findResource('tests://') . '/fake/simple-site/user/pages/01.home', $this->pages->routes()['/']); + $this->assertSame($locator->findResource('tests://') . '/fake/simple-site/user/pages/01.home', $this->pages->routes()['/home']); + $this->assertSame($locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog', $this->pages->routes()['/blog']); + $this->assertSame($locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-one', $this->pages->routes()['/blog/post-one']); + $this->assertSame($locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-two', $this->pages->routes()['/blog/post-two']); + $this->assertSame($locator->findResource('tests://') . '/fake/simple-site/user/pages/03.about', $this->pages->routes()['/about']); + } + + public function testAddPage() + { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + + $path = $locator->findResource('tests://') . '/fake/single-pages/01.simple-page/default.md'; + $aPage = new Page(); + $aPage->init(new \SplFileInfo($path)); + + $this->pages->addPage($aPage, '/new-page'); + + $this->assertTrue(in_array('/new-page', array_keys($this->pages->routes()))); + $this->assertSame($locator->findResource('tests://') . '/fake/single-pages/01.simple-page', $this->pages->routes()['/new-page']); + } + + public function testSort() + { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + + $aPage = $this->pages->dispatch('/blog'); + $subPagesSorted = $this->pages->sort($aPage); + + $this->assertTrue(is_array($subPagesSorted)); + $this->assertTrue(count($subPagesSorted) === 2); + + $this->assertSame($locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-one', array_keys($subPagesSorted)[0]); + $this->assertSame($locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-two', array_keys($subPagesSorted)[1]); + + $this->assertTrue(in_array($locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-one', array_keys($subPagesSorted))); + $this->assertTrue(in_array($locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-two', array_keys($subPagesSorted))); + + $this->assertSame(["slug" => "post-one"], $subPagesSorted[$locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-one']); + $this->assertSame(["slug" => "post-two"], $subPagesSorted[$locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-two']); + + $subPagesSorted = $this->pages->sort($aPage, null, 'desc'); + + $this->assertTrue(is_array($subPagesSorted)); + $this->assertTrue(count($subPagesSorted) === 2); + + $this->assertSame($locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-two', array_keys($subPagesSorted)[0]); + $this->assertSame($locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-one', array_keys($subPagesSorted)[1]); + + $this->assertTrue(in_array($locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-one', array_keys($subPagesSorted))); + $this->assertTrue(in_array($locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-two', array_keys($subPagesSorted))); + + $this->assertSame(["slug" => "post-one"], $subPagesSorted[$locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-one']); + $this->assertSame(["slug" => "post-two"], $subPagesSorted[$locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-two']); + } + + public function testSortCollection() + { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + + $aPage = $this->pages->dispatch('/blog'); + $subPagesSorted = $this->pages->sortCollection($aPage->children(), $aPage->orderBy()); + + $this->assertTrue(is_array($subPagesSorted)); + $this->assertTrue(count($subPagesSorted) === 2); + + $this->assertSame($locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-one', array_keys($subPagesSorted)[0]); + $this->assertSame($locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-two', array_keys($subPagesSorted)[1]); + + $this->assertTrue(in_array($locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-one', array_keys($subPagesSorted))); + $this->assertTrue(in_array($locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-two', array_keys($subPagesSorted))); + + $this->assertSame(["slug" => "post-one"], $subPagesSorted[$locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-one']); + $this->assertSame(["slug" => "post-two"], $subPagesSorted[$locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-two']); + + $subPagesSorted = $this->pages->sortCollection($aPage->children(), $aPage->orderBy(), 'desc'); + + $this->assertTrue(is_array($subPagesSorted)); + $this->assertTrue(count($subPagesSorted) === 2); + + $this->assertSame($locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-two', array_keys($subPagesSorted)[0]); + $this->assertSame($locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-one', array_keys($subPagesSorted)[1]); + + $this->assertTrue(in_array($locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-one', array_keys($subPagesSorted))); + $this->assertTrue(in_array($locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-two', array_keys($subPagesSorted))); + + $this->assertSame(["slug" => "post-one"], $subPagesSorted[$locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-one']); + $this->assertSame(["slug" => "post-two"], $subPagesSorted[$locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog/post-two']); + } + + public function testGet() + { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + + //Page existing + $aPage = $this->pages->get($locator->findResource('tests://') . '/fake/simple-site/user/pages/03.about'); + $this->assertTrue(is_object($aPage)); + $this->assertInstanceOf('Grav\Common\Page\Page', $aPage); + + //Page not existing + $anotherPage = $this->pages->get($locator->findResource('tests://') . '/fake/simple-site/user/pages/03.non-existing'); + $this->assertFalse(is_object($anotherPage)); + $this->assertNull($anotherPage); + } + + public function testChildren() + { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + + //Page existing + $children = $this->pages->children($locator->findResource('tests://') . '/fake/simple-site/user/pages/02.blog'); + $this->assertInstanceOf('Grav\Common\Page\Collection', $children); + + //Page not existing + $children = $this->pages->children($locator->findResource('tests://') . '/fake/whatever/non-existing'); + $this->assertSame([], $children->toArray()); + } + + public function testDispatch() + { + $aPage = $this->pages->dispatch('/blog'); + $this->assertInstanceOf('Grav\Common\Page\Page', $aPage); + + $aPage = $this->pages->dispatch('/about'); + $this->assertInstanceOf('Grav\Common\Page\Page', $aPage); + + $aPage = $this->pages->dispatch('/blog/post-one'); + $this->assertInstanceOf('Grav\Common\Page\Page', $aPage); + + //Page not existing + $aPage = $this->pages->dispatch('/non-existing'); + $this->assertNull($aPage); + } + + public function testRoot() + { + $root = $this->pages->root(); + $this->assertInstanceOf('Grav\Common\Page\Page', $root); + $this->assertSame('pages', $root->folder()); + } + + public function testBlueprints() + { + + } + + public function testAll() + { + $this->assertTrue(is_object($this->pages->all())); + $this->assertTrue(is_array($this->pages->all()->toArray())); + foreach($this->pages->all() as $page) { + $this->assertInstanceOf('Grav\Common\Page\Page', $page); + } + } + + public function testGetList() + { + $list = $this->pages->getList(); + $this->assertTrue(is_array($list)); + $this->assertSame('Home', $list['/']); + $this->assertSame('Blog', $list['/blog']); + } + + public function testGetTypes() + { + + } + + public function testTypes() + { + + } + + public function testModularTypes() + { + + } + + public function testPageTypes() + { + + } + + public function testAccessLevels() + { + + } + + public function testParents() + { + + } + + public function testParentsRawRoutes() + { + + } + + public function testGetHomeRoute() + { + + } + + public function testResetPages() + { + + } + +} diff --git a/tests/unit/Grav/Common/UriTest.php b/tests/unit/Grav/Common/UriTest.php new file mode 100644 index 0000000..3d40469 --- /dev/null +++ b/tests/unit/Grav/Common/UriTest.php @@ -0,0 +1,414 @@ +grav = Fixtures::get('grav'); + $this->uri = $this->grav['uri']; + } + + protected function _after() + { + } + + public function testValidatingHostname() + { + $this->assertTrue($this->uri->validateHostname('localhost') == 1); + $this->assertTrue($this->uri->validateHostname('google.com') == 1); + $this->assertTrue($this->uri->validateHostname('google.it') == 1); + $this->assertTrue($this->uri->validateHostname('goog.le') == 1); + $this->assertTrue($this->uri->validateHostname('goog.wine') == 1); + $this->assertTrue($this->uri->validateHostname('goog.localhost') == 1); + + $this->assertFalse($this->uri->validateHostname('localhost:80') == 1); + $this->assertFalse($this->uri->validateHostname('http://localhost') == 1); + $this->assertFalse($this->uri->validateHostname('localhost!') == 1); + } + + public function testInitializingUris() + { + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper')->init(); + $this->assertTrue($this->uri->params() == null); + $this->assertTrue($this->uri->query() == ''); + + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper:xxx')->init(); + $this->assertFalse($this->uri->params() == null); + $this->assertTrue($this->uri->query() == ''); + + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x')->init(); + + $this->assertTrue($this->uri->params() == null); + $this->assertTrue($this->uri->query() != ''); + $this->assertTrue($this->uri->query() == 'test=x'); + $this->assertTrue($this->uri->port() == '8080'); + + $this->uri->initializeWithURL('http://localhost:80/grav/it/ueper?test=x')->init(); + $this->assertTrue($this->uri->port() == '80'); + + $this->uri->initializeWithURL('http://localhost/grav/it/ueper?test=x')->init(); + $this->assertTrue($this->uri->port() == '80'); + + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper')->init(); + $this->assertTrue($this->uri->params() == null); + + $this->uri->initializeWithURL('http://grav/grav/it/ueper')->init(); + $this->assertTrue($this->uri->params() == null); + } + + public function testPaths() + { + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper')->init(); + $this->assertSame(['grav', 'it', 'ueper'], $this->uri->paths()); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper:xxx')->init(); + $this->assertSame(['grav', 'it'], $this->uri->paths()); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x')->init(); + $this->assertSame(['grav', 'it', 'ueper'], $this->uri->paths()); + $this->uri->initializeWithURL('http://localhost:8080/a/b/c/d')->init(); + $this->assertSame(['a', 'b', 'c', 'd'], $this->uri->paths()); + $this->uri->initializeWithURL('http://localhost:8080/a/b/c/d/e/f/a/b/c/d/e/f/a/b/c/d/e/f')->init(); + $this->assertSame(['a', 'b', 'c', 'd', 'e', 'f', 'a', 'b', 'c', 'd', 'e', 'f', 'a', 'b', 'c', 'd', 'e', 'f'], + $this->uri->paths()); + } + + public function testRoute() + { + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper')->init(); + $this->assertSame('/grav/it/ueper', $this->uri->route()); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper:xxx')->init(); + $this->assertSame('/grav/it', $this->uri->route()); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x')->init(); + $this->assertSame('/grav/it/ueper', $this->uri->route()); + $this->uri->initializeWithURL('http://localhost:8080/a/b/c/d')->init(); + $this->assertSame('/a/b/c/d', $this->uri->route()); + $this->uri->initializeWithURL('http://localhost:8080/a/b/c/d/e/f/a/b/c/d/e/f/a/b/c/d/e/f')->init(); + $this->assertSame('/a/b/c/d/e/f/a/b/c/d/e/f/a/b/c/d/e/f', $this->uri->route()); + } + + public function testQuery() + { + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper')->init(); + $this->assertSame('', $this->uri->query()); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper:xxx')->init(); + $this->assertSame('', $this->uri->query()); + $this->assertSame(null, $this->uri->query('id')); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x')->init(); + $this->assertSame('test=x', $this->uri->query()); + $this->assertSame(null, $this->uri->query('id')); + $this->assertSame('x', $this->uri->query('test')); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x&test2=y')->init(); + $this->assertSame('test=x&test2=y', $this->uri->query()); + $this->assertSame(null, $this->uri->query('id')); + $this->assertSame('x', $this->uri->query('test')); + $this->assertSame('y', $this->uri->query('test2')); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x&test2=y&test3=x&test4=y')->init(); + $this->assertSame('test=x&test2=y&test3=x&test4=y', $this->uri->query()); + $this->assertSame(null, $this->uri->query('id')); + $this->assertSame('x', $this->uri->query('test')); + $this->assertSame('y', $this->uri->query('test2')); + $this->assertSame('y', $this->uri->query('test4')); + //Test all after the ? is encoded in the query + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x&test2=y&test3=x&test4=y/test')->init(); + $this->assertSame('test=x&test2=y&test3=x&test4=y%2Ftest', $this->uri->query()); + $this->assertSame(null, $this->uri->query('id')); + $this->assertSame('x', $this->uri->query('test')); + $this->assertSame('y', $this->uri->query('test2')); + $this->assertSame('y/test', $this->uri->query('test4')); + $this->uri->initializeWithURL('http://localhost:8080/a/b/c/d')->init(); + $this->assertSame('', $this->uri->query()); + $this->assertSame(null, $this->uri->query('id')); + $this->uri->initializeWithURL('http://localhost:8080/a/b/c/d/e/f/a/b/c/d/e/f/a/b/c/d/e/f')->init(); + $this->assertSame('', $this->uri->query()); + $this->assertSame(null, $this->uri->query('id')); + } + + public function testParams() + { + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper')->init(); + $this->assertSame(null, $this->uri->params()); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper:xxx')->init(); + $this->assertSame('/ueper:xxx', $this->uri->params()); + $this->assertSame('/ueper:xxx', $this->uri->params('ueper')); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper:xxx/test:yyy')->init(); + $this->assertSame('/ueper:xxx/test:yyy', $this->uri->params()); + $this->assertSame('/ueper:xxx', $this->uri->params('ueper')); + $this->assertSame('/test:yyy', $this->uri->params('test')); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x')->init(); + $this->assertSame(null, $this->uri->params()); + $this->assertSame(null, $this->uri->params('ueper')); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x&test2=y')->init(); + $this->assertSame(null, $this->uri->params()); + $this->assertSame(null, $this->uri->params('ueper')); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x&test2=y&test3=x&test4=y')->init(); + $this->assertSame(null, $this->uri->params()); + $this->assertSame(null, $this->uri->params('ueper')); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x&test2=y&test3=x&test4=y/test')->init(); + $this->assertSame(null, $this->uri->params()); + $this->assertSame(null, $this->uri->params('ueper')); + $this->uri->initializeWithURL('http://localhost:8080/a/b/c/d')->init(); + $this->assertSame(null, $this->uri->params()); + $this->assertSame(null, $this->uri->params('ueper')); + $this->uri->initializeWithURL('http://localhost:8080/a/b/c/d/e/f/a/b/c/d/e/f/a/b/c/d/e/f')->init(); + $this->assertSame(null, $this->uri->params()); + $this->assertSame(null, $this->uri->params('ueper')); + } + + public function testParam() + { + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper:xxx')->init(); + $this->assertSame('xxx', $this->uri->param('ueper')); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper:xxx/test:yyy')->init(); + $this->assertSame('xxx', $this->uri->param('ueper')); + $this->assertSame('yyy', $this->uri->param('test')); + } + + public function testUrl() + { + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper')->init(); + $this->assertSame('/grav/it/ueper', $this->uri->url()); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper:xxx')->init(); + $this->assertSame('/grav/it', $this->uri->url()); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper:xxx/test:yyy')->init(); + $this->assertSame('/grav/it', $this->uri->url()); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x')->init(); + $this->assertSame('/grav/it/ueper', $this->uri->url()); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x&test2=y')->init(); + $this->assertSame('/grav/it/ueper', $this->uri->url()); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x&test2=y&test3=x&test4=y')->init(); + $this->assertSame('/grav/it/ueper', $this->uri->url()); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x&test2=y&test3=x&test4=y/test')->init(); + $this->assertSame('/grav/it/ueper', $this->uri->url()); + $this->uri->initializeWithURL('http://localhost:8080/a/b/c/d')->init(); + $this->assertSame('/a/b/c/d', $this->uri->url()); + $this->uri->initializeWithURL('http://localhost:8080/a/b/c/d/e/f/a/b/c/d/e/f/a/b/c/d/e/f')->init(); + $this->assertSame('/a/b/c/d/e/f/a/b/c/d/e/f/a/b/c/d/e/f', $this->uri->url()); + } + + public function testPath() + { + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper')->init(); + $this->assertSame('/grav/it/ueper', $this->uri->path()); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper:xxx')->init(); + $this->assertSame('/grav/it', $this->uri->path()); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper:xxx/test:yyy')->init(); + $this->assertSame('/grav/it', $this->uri->path()); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x')->init(); + $this->assertSame('/grav/it/ueper', $this->uri->path()); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x&test2=y')->init(); + $this->assertSame('/grav/it/ueper', $this->uri->path()); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x&test2=y&test3=x&test4=y')->init(); + $this->assertSame('/grav/it/ueper', $this->uri->path()); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x&test2=y&test3=x&test4=y/test')->init(); + $this->assertSame('/grav/it/ueper', $this->uri->path()); + $this->uri->initializeWithURL('http://localhost:8080/a/b/c/d')->init(); + $this->assertSame('/a/b/c/d', $this->uri->path()); + $this->uri->initializeWithURL('http://localhost:8080/a/b/c/d/e/f/a/b/c/d/e/f/a/b/c/d/e/f')->init(); + $this->assertSame('/a/b/c/d/e/f/a/b/c/d/e/f/a/b/c/d/e/f', $this->uri->path()); + $this->uri->initializeWithURL('http://localhost/')->init(); + $this->assertSame('/', $this->uri->path()); + } + + public function testExtension() + { + $this->uri->initializeWithURL('http://localhost/a-page')->init(); + $this->assertSame(null, $this->uri->extension()); + $this->uri->initializeWithURL('http://localhost/a-page')->init(); + $this->assertSame('x', $this->uri->extension('x')); + $this->uri->initializeWithURL('http://localhost/a-page.html')->init(); + $this->assertSame('html', $this->uri->extension()); + $this->uri->initializeWithURL('http://localhost/a-page.xml')->init(); + $this->assertSame('xml', $this->uri->extension()); + $this->uri->initializeWithURL('http://localhost/a-page.foo')->init(); + $this->assertSame('foo', $this->uri->extension()); + } + + public function testHost() + { + $address = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '::1'; + if ($this->uri->host() == 'localhost' || $address == '::1' || $address == '127.0.0.1') { + $address = 'localhost'; + } + + $this->uri->initializeWithURL('http://localhost/a-page')->init(); + $this->assertSame($address, $this->uri->host()); + $this->uri->initializeWithURL('http://localhost/')->init(); + $this->assertSame($address, $this->uri->host()); + } + + public function testPort() + { + $this->uri->initializeWithURL('http://localhost/a-page')->init(); + $this->assertSame('80', $this->uri->port()); + $this->uri->initializeWithURL('http://localhost:8080/a-page')->init(); + $this->assertSame(8080, $this->uri->port()); + $this->uri->initializeWithURL('http://localhost:443/a-page')->init(); + $this->assertSame(443, $this->uri->port()); + $this->uri->initializeWithURL('https://localhost/a-page')->init(); + $this->assertSame('80', $this->uri->port()); + } + + public function testEnvironment() + { + $address = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '::1'; + if ($this->uri->host() == 'localhost' || $address == '::1' || $address == '127.0.0.1') { + $address = 'localhost'; + } + + $this->uri->initializeWithURL('http://localhost/a-page')->init(); + $this->assertSame($address, $this->uri->environment()); + $this->uri->initializeWithURL('http://localhost:8080/a-page')->init(); + $this->assertSame($address, $this->uri->environment()); + $this->uri->initializeWithURL('http://foobar.it:443/a-page')->init(); + $this->assertSame($address, $this->uri->environment()); + $this->uri->initializeWithURL('https://google.com/a-page')->init(); + $this->assertSame($address, $this->uri->environment()); + } + + public function testBasename() + { + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper')->init(); + $this->assertSame('ueper', $this->uri->basename()); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper:xxx')->init(); + $this->assertSame('it', $this->uri->basename()); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper:xxx/test:yyy')->init(); + $this->assertSame('it', $this->uri->basename()); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x')->init(); + $this->assertSame('ueper', $this->uri->basename()); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x&test2=y')->init(); + $this->assertSame('ueper', $this->uri->basename()); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x&test2=y&test3=x&test4=y')->init(); + $this->assertSame('ueper', $this->uri->basename()); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x&test2=y&test3=x&test4=y/test')->init(); + $this->assertSame('ueper', $this->uri->basename()); + $this->uri->initializeWithURL('http://localhost:8080/a/b/c/d')->init(); + $this->assertSame('d', $this->uri->basename()); + $this->uri->initializeWithURL('http://localhost:8080/a/b/c/d/e/f/a/b/c/d/e/f/a/b/c/d/e/f')->init(); + $this->assertSame('f', $this->uri->basename()); + $this->uri->initializeWithURL('http://localhost/')->init(); + $this->assertSame('', $this->uri->basename()); + $this->uri->initializeWithURL('http://localhost/test.xml')->init(); + $this->assertSame('test.xml', $this->uri->basename()); + } + + public function testBase() + { + $this->uri->initializeWithURL('http://localhost/a-page')->init(); + $this->assertSame('http://localhost', $this->uri->base()); + $this->uri->initializeWithURL('http://localhost:8080/a-page')->init(); + $this->assertSame('http://localhost:8080', $this->uri->base()); + $this->uri->initializeWithURL('http://foobar.it:80/a-page')->init(); + $this->assertSame('http://foobar.it', $this->uri->base()); + $this->uri->initializeWithURL('https://google.com/a-page')->init(); + $this->assertSame('http://google.com', $this->uri->base()); + } + + public function testRootUrl() + { + //Without explicitly adding the root path via `initializeWithUrlAndRootPath`, + //tests always default to the base empty root path + $this->uri->initializeWithURL('http://localhost/a-page')->init(); + $this->assertSame('http://localhost', $this->uri->rootUrl(true)); + $this->uri->initializeWithURL('http://localhost:8080/a-page')->init(); + $this->assertSame('http://localhost:8080', $this->uri->rootUrl(true)); + $this->uri->initializeWithURL('http://foobar.it:80/a-page')->init(); + $this->assertSame('http://foobar.it', $this->uri->rootUrl(true)); + $this->uri->initializeWithURL('https://google.com/a-page/xxx')->init(); + $this->assertSame('http://google.com', $this->uri->rootUrl(true)); + + $this->uri->initializeWithUrlAndRootPath('https://localhost/grav/page-foo', '/grav')->init(); + $this->assertSame('/grav', $this->uri->rootUrl()); + $this->assertSame('http://localhost/grav', $this->uri->rootUrl(true)); + } + + public function testCurrentPage() + { + $this->uri->initializeWithURL('http://localhost/foo/page:test')->init(); + $this->assertSame('test', $this->uri->currentPage()); + $this->uri->initializeWithURL('http://localhost:8080/a-page')->init(); + $this->assertSame(1, $this->uri->currentPage()); + $this->uri->initializeWithURL('http://localhost:8080/a-page/page:2')->init(); + $this->assertSame('2', $this->uri->currentPage()); + $this->uri->initializeWithURL('http://localhost:8080/a-page/page:x')->init(); + $this->assertSame('x', $this->uri->currentPage()); + $this->uri->initializeWithURL('http://localhost:8080/a-page/page:')->init(); + $this->assertSame('', $this->uri->currentPage()); + } + + public function testReferrer() + { + $this->uri->initializeWithURL('http://localhost/foo/page:test')->init(); + $this->assertSame('/foo', $this->uri->referrer()); + $this->uri->initializeWithURL('http://localhost/foo/bar/page:test')->init(); + $this->assertSame('/foo/bar', $this->uri->referrer()); + } + + public function testIp() + { + $this->uri->initializeWithURL('http://localhost/foo/page:test')->init(); + $this->assertSame('UNKNOWN', $this->uri->ip()); + } + + public function testIsExternal() + { + $this->uri->initializeWithURL('http://localhost/')->init(); + $this->assertFalse($this->uri->isExternal('/test')); + $this->assertFalse($this->uri->isExternal('/foo/bar')); + $this->assertTrue($this->uri->isExternal('http://localhost/test')); + $this->assertTrue($this->uri->isExternal('http://google.it/test')); + } + + public function testBuildUrl() + { + $parsed_url = [ + 'scheme' => 'http', + 'host' => 'localhost', + 'port' => '8080', + ]; + + $this->assertSame('http://localhost:8080', Uri::buildUrl($parsed_url)); + + $parsed_url = [ + 'scheme' => 'http', + 'host' => 'localhost', + 'port' => '8080', + 'user' => 'foo', + 'pass' => 'bar', + 'path' => '/test', + 'query' => 'x=2', + 'fragment' => 'xxx', + ]; + + $this->assertSame('http://foo:bar@localhost:8080/test?x=2#xxx', Uri::buildUrl($parsed_url)); + } + + public function testConvertUrl() + { + + } + + public function testAddNonce() + { + $url = 'http://localhost/foo'; + $this->assertStringStartsWith($url, Uri::addNonce($url, 'test-action')); + $this->assertStringStartsWith($url . '/nonce:', Uri::addNonce($url, 'test-action')); + + $this->uri->initializeWithURL(Uri::addNonce($url, 'test-action'))->init(); + $this->assertTrue(is_string($this->uri->param('nonce'))); + $this->assertSame(Utils::getNonce('test-action'), $this->uri->param('nonce')); + } +} + diff --git a/tests/unit/Grav/Common/UtilsTest.php b/tests/unit/Grav/Common/UtilsTest.php new file mode 100644 index 0000000..9676128 --- /dev/null +++ b/tests/unit/Grav/Common/UtilsTest.php @@ -0,0 +1,263 @@ +grav = Fixtures::get('grav'); + } + + protected function _after() + { + } + + public function testStartsWith() + { + $this->assertTrue(Utils::startsWith('english', 'en')); + $this->assertTrue(Utils::startsWith('English', 'En')); + $this->assertTrue(Utils::startsWith('ENGLISH', 'EN')); + $this->assertTrue(Utils::startsWith('ENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISH', + 'EN')); + + $this->assertFalse(Utils::startsWith('english', 'En')); + $this->assertFalse(Utils::startsWith('English', 'EN')); + $this->assertFalse(Utils::startsWith('ENGLISH', 'en')); + $this->assertFalse(Utils::startsWith('ENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISH', + 'e')); + } + + public function testEndsWith() + { + $this->assertTrue(Utils::endsWith('english', 'sh')); + $this->assertTrue(Utils::endsWith('EngliSh', 'Sh')); + $this->assertTrue(Utils::endsWith('ENGLISH', 'SH')); + $this->assertTrue(Utils::endsWith('ENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISH', + 'ENGLISH')); + + $this->assertFalse(Utils::endsWith('english', 'de')); + $this->assertFalse(Utils::endsWith('EngliSh', 'sh')); + $this->assertFalse(Utils::endsWith('ENGLISH', 'Sh')); + $this->assertFalse(Utils::endsWith('ENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISH', + 'DEUSTCH')); + } + + public function testContains() + { + $this->assertTrue(Utils::contains('english', 'nglis')); + $this->assertTrue(Utils::contains('EngliSh', 'gliSh')); + $this->assertTrue(Utils::contains('ENGLISH', 'ENGLI')); + $this->assertTrue(Utils::contains('ENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISH', + 'ENGLISH')); + + $this->assertFalse(Utils::contains('EngliSh', 'GLI')); + $this->assertFalse(Utils::contains('EngliSh', 'English')); + $this->assertFalse(Utils::contains('ENGLISH', 'SCH')); + $this->assertFalse(Utils::contains('ENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISH', + 'DEUSTCH')); + } + + public function testSubstrToString() + { + $this->assertEquals(Utils::substrToString('english', 'glish'), 'en'); + $this->assertEquals(Utils::substrToString('english', 'test'), 'english'); + $this->assertNotEquals(Utils::substrToString('english', 'lish'), 'en'); + } + + public function testMergeObjects() + { + $obj1 = new stdClass(); + $obj1->test1 = 'x'; + $obj2 = new stdClass(); + $obj2->test2 = 'y'; + + $objMerged = Utils::mergeObjects($obj1, $obj2); + + $this->assertObjectHasAttribute('test1', $objMerged); + $this->assertObjectHasAttribute('test2', $objMerged); + } + + public function testDateFormats() + { + $dateFormats = Utils::dateFormats(); + $this->assertTrue(is_array($dateFormats)); + $this->assertContainsOnly('string', $dateFormats); + + $default_format = $this->grav['config']->get('system.pages.dateformat.default'); + + if ($default_format !== null) { + $this->assertTrue(isset($dateFormats[$default_format])); + } + } + + public function testTruncate() + { + $this->assertEquals(Utils::truncate('english', 5), 'engli' . '…'); + $this->assertEquals(Utils::truncate('english'), 'english'); + $this->assertEquals(Utils::truncate('This is a string to truncate'), 'This is a string to truncate'); + $this->assertEquals(Utils::truncate('This is a string to truncate', 2), 'Th' . '…'); + $this->assertEquals(Utils::truncate('english', 5, true, " ", "..."), 'engli' . '...'); + $this->assertEquals(Utils::truncate('english'), 'english'); + $this->assertEquals(Utils::truncate('This is a string to truncate'), 'This is a string to truncate'); + $this->assertEquals(Utils::truncate('This is a string to truncate', 3, true), 'This '); + } + + public function testSafeTruncate() + { + $this->assertEquals(Utils::safeTruncate('This is a string to truncate', 1), 'This '); + $this->assertEquals(Utils::safeTruncate('This is a string to truncate', 4), 'This '); + $this->assertEquals(Utils::safeTruncate('This is a string to truncate', 5), 'This is '); + } + + public function testTruncateHtml() + { + $this->assertEquals(Utils::truncateHtml('

This is a string to truncate

', 1), '

T…

'); + $this->assertEquals(Utils::truncateHtml('

This is a string to truncate

', 4), '

This…

'); + } + + public function testSafeTruncateHtml() + { + $this->assertEquals(Utils::safeTruncateHtml('

This is a string to truncate

', 1), '

This…

'); + $this->assertEquals(Utils::safeTruncateHtml('

This is a string to truncate

', 4), '

This…

'); + } + + public function testGenerateRandomString() + { + $this->assertNotEquals(Utils::generateRandomString(), Utils::generateRandomString()); + $this->assertNotEquals(Utils::generateRandomString(20), Utils::generateRandomString(20)); + } + + public function download() + { + + } + + public function testGetMimeType() + { + $this->assertEquals(Utils::getMimeType(''), 'application/octet-stream'); + $this->assertEquals(Utils::getMimeType('jpg'), 'image/jpeg'); + $this->assertEquals(Utils::getMimeType('png'), 'image/png'); + $this->assertEquals(Utils::getMimeType('txt'), 'text/plain'); + $this->assertEquals(Utils::getMimeType('doc'), 'application/msword'); + } + + public function testNormalizePath() + { + $this->assertEquals(Utils::normalizePath('/test'), '/test'); + $this->assertEquals(Utils::normalizePath('test'), 'test'); + $this->assertEquals(Utils::normalizePath('../test'), 'test'); + $this->assertEquals(Utils::normalizePath('/../test'), '/test'); + $this->assertEquals(Utils::normalizePath('/test/../test2'), '/test2'); + $this->assertEquals(Utils::normalizePath('/test/./test2'), '/test/test2'); + } + + public function testIsFunctionDisabled() + { + $disabledFunctions = explode(',', ini_get('disable_functions')); + + if ($disabledFunctions[0]) { + $this->assertEquals(Utils::isFunctionDisabled($disabledFunctions[0]), true); + } + } + + public function testTimezones() + { + $timezones = Utils::timezones(); + + $this->assertTrue(is_array($timezones)); + $this->assertContainsOnly('string', $timezones); + } + + public function testArrayFilterRecursive() + { + $array = [ + 'test' => '', + 'test2' => 'test2' + ]; + + $array = Utils::arrayFilterRecursive($array, function ($k, $v) { + return !(is_null($v) || $v === ''); + }); + + $this->assertContainsOnly('string', $array); + $this->assertFalse(isset($array['test'])); + $this->assertTrue(isset($array['test2'])); + $this->assertEquals($array['test2'], 'test2'); + } + + public function testPathPrefixedByLangCode() + { + $languagesEnabled = $this->grav['config']->get('system.languages.supported', []); + $arrayOfLanguages = ['en', 'de', 'it', 'es', 'dk', 'el']; + $languagesNotEnabled = array_diff($arrayOfLanguages, $languagesEnabled); + $oneLanguageNotEnabled = reset($languagesNotEnabled); + + if (count($languagesEnabled)) { + $this->assertTrue(Utils::pathPrefixedByLangCode('/' . $languagesEnabled[0] . '/test')); + } + + $this->assertFalse(Utils::pathPrefixedByLangCode('/' . $oneLanguageNotEnabled . '/test')); + $this->assertFalse(Utils::pathPrefixedByLangCode('/test')); + $this->assertFalse(Utils::pathPrefixedByLangCode('/xx')); + $this->assertFalse(Utils::pathPrefixedByLangCode('/xx/')); + $this->assertFalse(Utils::pathPrefixedByLangCode('/')); + } + + public function testDate2timestamp() + { + $timestamp = strtotime('10 September 2000'); + $this->assertSame($timestamp, Utils::date2timestamp('10 September 2000')); + $this->assertSame($timestamp, Utils::date2timestamp('2000-09-10 00:00:00')); + } + + public function testResolve() + { + $array = [ + 'test' => [ + 'test2' => 'test2Value' + ] + ]; + + $this->assertEquals(Utils::resolve($array, 'test.test2'), 'test2Value'); + } + + public function testIsPositive() + { + $this->assertTrue(Utils::isPositive(true)); + $this->assertTrue(Utils::isPositive(1)); + $this->assertTrue(Utils::isPositive('1')); + $this->assertTrue(Utils::isPositive('yes')); + $this->assertTrue(Utils::isPositive('on')); + $this->assertTrue(Utils::isPositive('true')); + $this->assertFalse(Utils::isPositive(false)); + $this->assertFalse(Utils::isPositive(0)); + $this->assertFalse(Utils::isPositive('0')); + $this->assertFalse(Utils::isPositive('no')); + $this->assertFalse(Utils::isPositive('off')); + $this->assertFalse(Utils::isPositive('false')); + $this->assertFalse(Utils::isPositive('some')); + $this->assertFalse(Utils::isPositive(2)); + } + + public function testGetNonce() + { + $this->assertTrue(is_string(Utils::getNonce('test-action'))); + $this->assertTrue(is_string(Utils::getNonce('test-action', true))); + $this->assertSame(Utils::getNonce('test-action'), Utils::getNonce('test-action')); + $this->assertNotSame(Utils::getNonce('test-action'), Utils::getNonce('test-action2')); + } + + public function testVerifyNonce() + { + $this->assertTrue(Utils::verifyNonce(Utils::getNonce('test-action'), 'test-action')); + } +} \ No newline at end of file diff --git a/tests/unit/_bootstrap.php b/tests/unit/_bootstrap.php new file mode 100644 index 0000000..f92ef0c --- /dev/null +++ b/tests/unit/_bootstrap.php @@ -0,0 +1,4 @@ +` tag from page content + +# v1.0.1 +## 01/29/2015 + +1. [](#improved) + * Fixed minor issues (broken README link, removed debugging functions) + +# v1.0.0 +## 01/29/2015 + +1. [](#new) + * ChangeLog started... diff --git a/user/plugins/external_links/LICENSE b/user/plugins/external_links/LICENSE new file mode 100644 index 0000000..731ce27 --- /dev/null +++ b/user/plugins/external_links/LICENSE @@ -0,0 +1,727 @@ +Grav External Links Plugin License +================================== + +Grav External Links Plugin is meant to be a free and open source for all now +and in future. + +For ease of distribution, Grav External Links Plugin is licensed under a dual +license and is based on the principle of Give-and-Take. + +Unless otherwise noted Grav External Links Plugin is licensed under the terms +of GPLv3 (or see license text below). + +However it is licensed under the terms of MIT + + + +(or see license text below) when the following conditions are met: + + - Grav External Links Plugin is used, copied, merged, published for or + distributed with Grav (http://getgrav.org) + +and can be understood as a Free and Open Source Software ("FOSS") License +Exception especially for all plugins, themes, extensions available for Grav +using Grav External Links Plugin in any way. + + +MIT LICENSE +----------- + +Copyright (c) 2015 Benjamin Regler, https://github.com/sommerregen/grav-plugin-external-links + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +GPL VERSION 3 +------------- + +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/user/plugins/external_links/README.md b/user/plugins/external_links/README.md new file mode 100644 index 0000000..f5b9fd4 --- /dev/null +++ b/user/plugins/external_links/README.md @@ -0,0 +1,199 @@ +# [![Grav External Links Plugin](assets/logo.png)][project] + +[![Release](https://img.shields.io/github/release/sommerregen/grav-plugin-external-links.svg)][project] [![Issues](https://img.shields.io/github/issues/sommerregen/grav-plugin-external-links.svg)][issues] [![Dual license](https://img.shields.io/badge/dual%20license-MIT%2FGPL-blue.svg)](LICENSE "License") [![Flattr](https://api.flattr.com/button/flattr-badge-large.png)][flattr] [![PayPal](https://www.paypal.com/en_US/i/btn/btn_donate_SM.gif)][paypal] + +> This plugin adds small icons to external and mailto links, informing users the link will take them to a new site or open their email client. + +##### Table of Contents: + +* [About](#about) +* [Installation and Updates](#installation-and-updates) +* [Usage](#usage) + * [Example](#example) + * [CSS Stylesheet Override](#css-stylesheet-override) + * [Twig Filter](#twig-filter) +* [Contributing](#contributing) +* [License](#license) + +## About + +`External Links` is a high-quality plugin for [GetGrav.org](http://getgrav.org) used to differentiate between internal and external links. It also includes the ability of adding `rel="nofollow"` to links and determines how external links shall be opened via the `target` attribute. [Wikipedia](https://www.wikipedia.org/) is a well-known example. + +Furthermore it enables you to specify multiple domains, each of them on a new line to prevent them from being seen as external sites. + +If you are interested in seeing this plugin in action, here is a screenshot: + +![Screenshot External Links Plugin](assets/screenshot.png "External Links Preview") + +## Installation and Updates + +Installing or updating the `External Links` plugin can be done in one of two ways. Using the GPM (Grav Package Manager) installation update method (i.e. `bin/gpm install external_links`) or manual install by downloading [this plugin](https://github.com/sommerregen/grav-plugin-external-links) and extracting all plugin files to + + /your/site/grav/user/plugins/external_links + +For more informations, please check the [Installation and update guide](docs/INSTALL.md). + +## Usage + +The `External Links` plugin comes with some sensible default configuration, that are pretty self explanatory: + +### Config Defaults + +```yaml +# Global plugin configurations + +enabled: true # Set to false to disable this plugin completely +built_in_css: true # Use built-in CSS of the plugin +weight: 0 # Set the weight (order of execution) + +# Default options for External Links configuration + +exclude: + classes: [exclude] # Exclude all links with this class + domains: # A list of domains to be excluded e.g + # - localhost/* # (any RegExp can be used) + # - img.domain.com/* + +links: + www: true # Autom. link any hostname that starts with "www." + + schemes: # Allowed schemes + - 'http' + - 'https' + - 'ftp' + - 'irc' + - 'mailto' + - 'news' + - 'nntp' + - 'rtsp' + - 'sftp' + - 'ssh' + - 'tel' + - 'telnet' + - 'webcal' + +# Global and page specific configurations + +process: true # Filter external links + +title: false # Sets default title for external links +no_follow: true # Add rel="nofollow" to all external links +target: "_blank" # Set target attribute of link + +mode: "active" # active = process and parse all links; + # passive = parse links, but don't set CSS classes +``` + +If you need to change any value, then the best process is to copy the [external_links.yaml](external_links.yaml) file into your `users/config/plugins/` folder (create it if it doesn't exist), and then modify there. This will override the default settings. + +If you want to alter the settings for one or a few pages only, you can do so by adding page specific configurations into your page headers, e.g. + +```yaml +external_links: false +``` + +to switch off `External Links` plugin just for this page. + +### Example + +By default `External Links` adds a small icon to external or mailto links and opens by default links in a new tab. Any links in your Markdown file i.e. of the form + +```markdown +[External Link text](http://domain.com) + +or + +[External Link text](https://domain.com) +``` + +will then be recognized as an external link (an icon on the right is showing up), whereas + +```markdown +[My arcticle](my-article) +``` + +will not. You can exclude specific domains not to be seen as external either by adding them to the `exlcude.domains` option or by adding a class (default: `exclude`) to the desired links as in the following: + +```markdown +[Domain without external link icon](http://my-specific-domain.com) {.exclude} +``` + +> Please note that **Markdown Extra** should be enabled in your `user/config/systems.yaml` via `pages.markdown.extra: true` in order to add classes to these links. + +Sometimes you maybe wish to explicitly set a link to be "external". Although this should happen in really rare cases, since `External links` plugin will automatically detect external links, it is possible. Provided that **Markdown Extra** is enabled, adding the class `external` to the link will do the trick: + +```markdown +[Always show external link icon](my-external-article) {.external} +``` + +### CSS Stylesheet Override + +Something you might want to do is to override the look and feel of the external links, and with Grav it is super easy. + +Copy the stylesheet [assets/css/external_links.css](assets/css/external_links.css) into the `css` folder of your custom theme, and add it to the list of CSS files. + + user/themes/custom-theme/css/external_links.css + +After that set the `built_in_css` option of the `External Links` plugin to `false`. That's it. + +You can now edit, override and tweak it however you prefer. However, this plugin adds extra classes for styling to every link, you might wanna know: + +- `external`, `external-link` -- Used to identify external links. +- `mailto` -- Used to identify mailto links. +- `no-image` -- Set if a link does not contain any image tags. +- `icon`-- Set if a link contains an image (with size <= 32px). +- `image` -- Set if a link contains an image (with size > 32px). +- `images` -- Set if a link contains more than one image. + +### Twig Filter + +`External Links` provides a Twig filter to process external links inside contents. To do that, place the following line of code in the theme file you wish to add the `External Links` plugin for: + +``` +{{ page.content|external_links }} +``` + +You can pass arguments to the plugin filter, namely the same arguments as available in the [external_links.yaml](external_links.yaml) file. For example + +``` +{{ page.content|external_links({'mode': 'passive'}) }} +``` + +will change the mode of the `External Links` filter to parse links, but don't set CSS classes. + +## Contributing + +You can contribute at any time! Before opening any issue, please search for existing issues and review the [guidelines for contributing](docs/CONTRIBUTING.md). + +After that please note: + +* If you find a bug, would like to make a feature request or suggest an improvement, [please open a new issue][issues]. If you have any interesting ideas for additions to the syntax please do suggest them as well! +* Feature requests are more likely to get attention if you include a clearly described use case. +* If you wish to submit a pull request, please make again sure that your request match the [guidelines for contributing](docs/CONTRIBUTING.md) and that you keep track of adding unit tests for any new or changed functionality. + +### Support and donations + +If you like my project, feel free to support me via [![Flattr](https://api.flattr.com/button/flattr-badge-large.png)][flattr] or by sending me some bitcoins to [**1HQdy5aBzNKNvqspiLvcmzigCq7doGfLM4**][bitcoin]. + +Thanks! + +## License + +Copyright (c) 2015 [Benjamin Regler][github]. See also the list of [contributors] who participated in this project. + +[Dual-licensed](LICENSE) for use under the terms of the [MIT][mit-license] or [GPLv3][gpl-license] licenses. + +![GNU license - Some rights reserved][gnu] + +[github]: https://github.com/sommerregen/ "GitHub account from Benjamin Regler" +[gpl-license]: http://opensource.org/licenses/GPL-3.0 "GPLv3 license" +[mit-license]: http://www.opensource.org/licenses/mit-license.php "MIT license" + +[flattr]: https://flattr.com/submit/auto?user_id=Sommerregen&url=https://github.com/sommerregen/grav-plugin-external-links "Flatter my GitHub project" +[paypal]: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=SYFNP82USG3RN "Donate for my GitHub project using PayPal" +[bitcoin]: bitcoin:1HQdy5aBzNKNvqspiLvcmzigCq7doGfLM4?label=GitHub%20project "Donate for my GitHub project using BitCoin" +[gnu]: https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/License_icon-gpl-88x31.svg/88px-License_icon-gpl-88x31.svg.png "GNU license - Some rights reserved" + +[project]: https://github.com/sommerregen/grav-plugin-external-links +[issues]: https://github.com/sommerregen/grav-plugin-external-links/issues "GitHub Issues for Grav External Links Plugin" +[contributors]: https://github.com/sommerregen/grav-plugin-external-links/graphs/contributors "List of contributors of the project" diff --git a/user/plugins/external_links/assets/css/external_links.css b/user/plugins/external_links/assets/css/external_links.css new file mode 100644 index 0000000..c4f35de --- /dev/null +++ b/user/plugins/external_links/assets/css/external_links.css @@ -0,0 +1,39 @@ +a.external-link.icon, a.external-link.no-image, a.external, a.external { + background: url(../images/link.png) right center no-repeat; + padding-right: 12px; + position: relative; +} +a.mailto { + background: url(../images/mail.png) right center no-repeat; + padding-right: 12px; +} + +.external-link[data-title]:after { + background-color: #444; + color: #fff; + content: attr(data-title); + display: inline; + font-size: xx-small; + font-weight: bold; + margin: -1em 0 0 16px; + min-width: 45em; + opacity: 0; + padding: .25em .5em; + position: absolute; + top: 50%; + transition: all 1s ease-in-out 1s; + visibility: hidden; +} + +.external-link[data-title]:hover:after { + opacity: 1; + visibility: visible; +} + +/* Hide link or mailto icons when printing. */ +@media print { + a.external, a.external-link, a.mailto { + background-image: none; + padding: 0; + } +} diff --git a/user/plugins/external_links/assets/images/link.png b/user/plugins/external_links/assets/images/link.png new file mode 100644 index 0000000000000000000000000000000000000000..fb4f1f4364c4b09d77e005d27149ef4dadf456e1 GIT binary patch literal 196 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2VGmzZ%#=aj&v7|ftIx;X^yK%)o!U)KhEOCt} z3C>R|DNig)WpGT%PfAtr%uP&B4N6T+sVqF1Y6Dcn8Q>G*I&1@#VkvzAe-Et9=)!xF{)o|LMka>V^E{ mfGU=^mkX+dzpC}<9$*mXVaW8nP^|^j&EVf4NtU=qlmzFem6RtIr7}3Cp;2j^%;heKq!ot0=7~Xg!pD@uGq8`4wEI1`p3C jyFB?`G{f$XSOUW@UNe#DQj@mbP0l+XkKflNaZ literal 0 HcmV?d00001 diff --git a/user/plugins/external_links/assets/logo.png b/user/plugins/external_links/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..11a99fbdbff21c4f04e169241bcf7b2f29bd5dc4 GIT binary patch literal 21194 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 literal 0 HcmV?d00001 diff --git a/user/plugins/external_links/blueprints.yaml b/user/plugins/external_links/blueprints.yaml new file mode 100644 index 0000000..c9c1c37 --- /dev/null +++ b/user/plugins/external_links/blueprints.yaml @@ -0,0 +1,173 @@ +name: External Links +version: 1.4.2 +description: "This plugin adds small icons to external and mailto links, informing users the link will take them to a new site or open their email client." +icon: external-link +author: + name: Sommerregen + email: sommerregen@benjamin-regler.de +homepage: https://github.com/sommerregen/grav-plugin-external-links +keywords: [external, links, filter, formatter, plugin] +docs: https://github.com/sommerregen/grav-plugin-external-links/blob/master/README.md +bugs: https://github.com/sommerregen/grav-plugin-external-links/issues +license: MIT/GPL + +form: + validation: strict + fields: + global: + type: section + title: PLUGINS.EXTERNAL_LINKS.GLOBAL_CONFIG + underline: 1 + + fields: + enabled: + type: toggle + label: PLUGINS.EXTERNAL_LINKS.PLUGIN_STATUS + highlight: 1 + default: 0 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + built_in_css: + type: toggle + label: PLUGINS.EXTERNAL_LINKS.BUILTIN_CSS + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + weight: + type: text + size: x-small + label: PLUGINS.EXTERNAL_LINKS.WEIGHT + default: 0 + validate: + type: int + min: -100 + max: 100 + + default: + type: section + title: PLUGINS.EXTERNAL_LINKS.DEFAULT_CONFIG + underline: 1 + + fields: + links: + type: section + title: PLUGINS.EXTERNAL_LINKS.LINKS.SECTION + text: PLUGINS.EXTERNAL_LINKS.LINKS.SECTION_HELP + + fields: + links.www: + type: toggle + label: PLUGINS.EXTERNAL_LINKS.LINKS.WWW + help: PLUGINS.EXTERNAL_LINKS.LINKS.WWW_HELP + default: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + links.schemes: + type: selectize + size: large + label: PLUGINS.EXTERNAL_LINKS.LINKS.SCHEMES + help: PLUGINS.EXTERNAL_LINKS.LINKS.SCHEMES_HELP + validate: + type: commalist + + exclude: + type: section + title: PLUGINS.EXTERNAL_LINKS.EXCLUDE.SECTION + text: PLUGINS.EXTERNAL_LINKS.EXCLUDE.SECTION_HELP + + fields: + exclude.classes: + type: selectize + size: large + label: PLUGINS.EXTERNAL_LINKS.EXCLUDE.CLASSES + help: PLUGINS.EXTERNAL_LINKS.EXCLUDE.CLASSES_HELP + validate: + type: commalist + + exclude.domains: + type: selectize + size: large + label: PLUGINS.EXTERNAL_LINKS.EXCLUDE.DOMAINS + help: PLUGINS.EXTERNAL_LINKS.EXCLUDE.DOMAINS_HELP + placeholder: "localhost/*, img.domain.com/*" + validate: + type: commalist + + specific: + type: section + title: PLUGINS.EXTERNAL_LINKS.SPECIFIC_CONFIG + underline: 1 + + fields: + process: + type: toggle + label: PLUGINS.EXTERNAL_LINKS.PROCESS + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + title: + type: toggle + label: PLUGINS.EXTERNAL_LINKS.TITLE + default: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + no_follow: + type: toggle + label: PLUGINS.EXTERNAL_LINKS.NO_FOLLOW + default: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + target: + type: select + size: large + label: PLUGINS.EXTERNAL_LINKS.TARGET + default: _blank + placeholder: _blank + selectize: + create: true + options: + _blank: PLUGINS.EXTERNAL_LINKS.TARGET_BLANK + _self: PLUGINS.EXTERNAL_LINKS.TARGET_SELF + _parent: PLUGINS.EXTERNAL_LINKS.TARGET_PARENT + _top: PLUGINS.EXTERNAL_LINKS.TARGET_TOP + validate: + type: string + + mode: + type: select + size: large + classes: fancy + label: PLUGINS.EXTERNAL_LINKS.MODE + help: PLUGINS.EXTERNAL_LINKS.MODE_HELP + default: active + options: + active: PLUGINS.EXTERNAL_LINKS.MODE_ACTIVE + passive: PLUGINS.EXTERNAL_LINKS.MODE_PASSIVE + validate: + type: string diff --git a/user/plugins/external_links/blueprints/external_links.yaml b/user/plugins/external_links/blueprints/external_links.yaml new file mode 100644 index 0000000..c4701fc --- /dev/null +++ b/user/plugins/external_links/blueprints/external_links.yaml @@ -0,0 +1,78 @@ +form: + validation: loose + + fields: + tabs: + type: tabs + active: 1 + + fields: + options: + type: tab + + fields: + external_links: + type: section + title: PLUGINS.EXTERNAL_LINKS.EXTERNAL_LINKS + underline: true + + fields: + header.external_links.process: + type: toggle + label: PLUGINS.EXTERNAL_LINKS.PROCESS + highlight: 1 + '@config-default': plugins.external_links.process + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + header.external_links.title: + type: toggle + label: PLUGINS.EXTERNAL_LINKS.TITLE + '@config-default': plugins.external_links.title + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + header.external_links.no_follow: + type: toggle + label: PLUGINS.EXTERNAL_LINKS.NO_FOLLOW + '@config-default': plugins.external_links.no_follow + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + header.external_links.target: + type: select + size: large + label: PLUGINS.EXTERNAL_LINKS.TARGET + '@config-default': plugins.external_links.target + placeholder: _blank + selectize: + create: true + options: + _blank: PLUGINS.EXTERNAL_LINKS.TARGET_BLANK + _self: PLUGINS.EXTERNAL_LINKS.TARGET_SELF + _parent: PLUGINS.EXTERNAL_LINKS.TARGET_PARENT + _top: PLUGINS.EXTERNAL_LINKS.TARGET_TOP + validate: + type: string + + header.external_links.mode: + type: select + size: large + classes: fancy + label: PLUGINS.EXTERNAL_LINKS.MODE + help: PLUGINS.EXTERNAL_LINKS.MODE_HELP + '@config-default': plugins.external_links.mode + options: + active: PLUGINS.EXTERNAL_LINKS.MODE_ACTIVE + passive: PLUGINS.EXTERNAL_LINKS.MODE_PASSIVE + validate: + type: string \ No newline at end of file diff --git a/user/plugins/external_links/classes/ExternalLinks.php b/user/plugins/external_links/classes/ExternalLinks.php new file mode 100644 index 0000000..312f324 --- /dev/null +++ b/user/plugins/external_links/classes/ExternalLinks.php @@ -0,0 +1,485 @@ + tags and process them + $content = preg_replace_callback('~]*>.*?~i', + function($match) use ($options, $page) { + // Load PHP built-in DOMDocument class + if (($dom = $this->loadDOMDocument($match[0])) === null) { + return $match[0]; + } + + $a = $dom->getElementsByTagName('a')->item(0); + + // Process links with non-empty href attribute + $href = $a->getAttribute('href'); + if (strlen($href) == 0) { + return $match[0]; + } + + // Get the class of the element + $class = $a->hasAttribute('class') ? $a->getAttribute('class') : ''; + $classes = array_filter(explode(' ', $class)); + + // Exclude links with specific class from processing + $exclude = $options->get('exclude.classes', null); + if ($exclude && !!array_intersect($exclude, $classes)) { + return $match[0]; + } + + // Get domains to be seen as internal + $domains = $options->get('exclude.domains', []); + + // This is a mailto link. + if (strpos($href, 'mailto:') === 0) { + $classes[] = 'mailto'; + } + + // The link is external + elseif ($url = $this->isExternalUrl($href, $domains, $page)) { + // Add external class + $classes[] = 'external-link'; + $a->setAttribute('href', $url); + + // Add target="_blank" + $target = $options->get('target'); + if ($target) { + $a->setAttribute('target', $target); + } + + // Add no-follow. + $nofollow = $options->get('no_follow'); + if ($nofollow) { + $rel = array_filter(explode(' ', $a->getAttribute('rel'))); + if (!in_array('nofollow', $rel)) { + $rel[] = 'nofollow'; + $a->setAttribute('rel', implode(' ', $rel)); + } + } + + // Add image class to if it has at least one child element + $imgs = $a->getElementsByTagName('img'); + if ($imgs->length > 1) { + // Add "images" class to element, if it has multiple child images + $classes[] = 'images'; + } elseif ($imgs->length == 1) { + $imgNode = $imgs->item(0); + + // Get image size + list($width, $height) = $this->getImageSize($imgNode); + + // Determine maximum dimension of image size + $size = max($width, $height); + + // Depending on size determine image type + $classes[] = ((0 < $size) && ($size <= 32)) ? 'icon' : 'image'; + } else { + // Add "no-image" class to element, if it has no child images + $classes[] = 'no-image'; + } + + // Add title (aka alert text) + if ($options->get('title')) { + $language = self::getGrav()['language']; + $message = $language->translate(['PLUGINS.EXTERNAL_LINKS.TITLE_MESSAGE']); + + // Set default title to link else, set title as data attribute + $key = $a->hasAttribute('title') ? 'data-title' : 'title'; + $a->setAttribute($key, $message); + } + } + + // Set class attribute + if (count($classes) && ($options->get('mode') === 'active')) { + $a->setAttribute('class', implode(' ', $classes)); + } + + // Save Dom document back to HTML representation + $html = $this->saveDOMDocument($dom); + return $html; + }, $content); + + // Write content back to page + return $content; + } + + /** ------------------------------- + * Private/protected helper methods + * -------------------------------- + */ + + /** + * Test if a URL is external + * + * @param string $url The URL to test. + * @param array $domains An array of domains to be seen as internal. + * @param null|Page $page Null or an instance of \Grav\Common\Page. + * + * @return mixed Returns the URL as a string, if it is external, + * false otherwise. + */ + protected function isExternalUrl($url, $domains = [], $page = null) + { + static $allowed_protocols; + static $pattern; + + /** @var Config $config */ + $config = self::getGrav()['config']; + + /** @var Page $page */ + $page = $page ?: self::getGrav()['page']; + + // Statically store allowed protocols + if (!isset($allowed_protocols)) { + $allowed_protocols = array_flip( + $config->get('plugins.external_links.links.schemes', ['http', 'https']) + ); + } + + // Statically store internal domains as a PCRE pattern. + if (!isset($pattern) || (count($domains) > 0)) { + $domains = array_merge($domains, + array(self::getGrav()['base_url_absolute'])); + + foreach ($domains as $domain) { + $domains[] = preg_quote($domain, '#'); + } + $pattern = '#(' . str_replace(array('\*', '/*'), '.*?', + implode('|', $domains)) . ')#i'; + } + + $external = false; + // Check for URLs that don't match any excluded domain + if (!preg_match($pattern, $url)) { + // Check if URL is external by extracting colon position + $colonpos = strpos($url, ':'); + if ($colonpos > 0) { + // We found a colon, possibly a protocol. Verify. + $protocol = strtolower(substr($url, 0, $colonpos)); + if (isset($allowed_protocols[$protocol])) { + // The protocol turns out be an allowed protocol + $external = $url; + } + } elseif ($config->get('plugins.external_links.links.www')) { + // Remove possible path duplicate + $route = self::getGrav()['base_url'] . $page->route(); + $href = Utils::startsWith($url, $route) + ? ltrim(mb_substr($url, mb_strlen($route)), '/') + : $url; + + // We found an url without protocol, but with starting 'www' (sub-)domain + if (Utils::startsWith($url, 'www.')) { + $external = 'http://' . $url; + } elseif (Utils::startsWith($href, 'www.')) { + $external = 'http://' . $href; + } + } + } + + // Only if a valid protocol or an URL starting with 'www.' was found return true + return $external; + } + + /** + * Determine the size of an image + * + * @param DOMNode $imgNode The image already parsed as a DOMNode + * @param integer $limit Load first $limit KB of remote image + * + * @return array Return the dimension of the image of the + * format array(width, height) + */ + protected function getImageSize($imgNode, $limit = 32) + { + // Hold units (assume standard font with 16px base pixel size) + // Calculations based on pixels + $units = array( + 'px' => 1, /* base unit: pixel */ + 'pt' => 16 / 12, /* 12 point = 16 pixel = 1/72 inch */ + 'pc' => 16, /* 1 pica = 16 pixel = 12 points */ + + 'in' => 96, /* 1 inch = 96 pixel = 2.54 centimeters */ + 'mm' => 96 / 25.4, /* 1 millimeter = 96 pixel / 1 inch [mm] */ + 'cm' => 96 / 2.54, /* 1 centimeter = 96 pixel / 1 inch [cm] */ + 'm' => 96 / 0.0254, /* 1 centimeter = 96 pixel / 1 inch [m] */ + + 'ex' => 7, /* 1 ex = 7 pixel */ + 'em' => 16, /* 1 em = 16 pixel */ + 'rem' => 16, /* 1 rem = 16 pixel */ + + '%' => 16 / 100, /* 100 percent = 16 pixel */ + ); + + // Initialize dimensions + $width = 0; + $height = 0; + + // Determine image dimensions based on "src" atrribute + if ($imgNode->hasAttribute('src')) { + $src = $imgNode->getAttribute('src'); + + // Simple check if the URL is internal i.e. check if path exists + $path = $_SERVER['DOCUMENT_ROOT'] . $src; + if (realpath($path) && is_file($path)) { + $size = @getimagesize($path); + } else { + // The URL is external; try to load it (default: 32 KB) + $size = $this->getRemoteImageSize($src, $limit * 1024); + } + } + + // Read out width and height from attributes + $width = $imgNode->hasAttribute('width') ? + $imgNode->getAttribute('width') : $size[0]; + $height = $imgNode->hasAttribute('height') ? + $imgNode->getAttribute('height') : $size[1]; + + // Get width and height from style attribute + if ( $imgNode->hasAttribute('style') ) { + $style = $imgNode->getAttribute('style'); + + // Width + if (preg_match('~width:\s*(\d+)([a-z]+)~i', $style, $matches)) { + $width = $matches[1]; + // Convert unit to pixel + if ( isset($units[$matches[2]]) ) { + $width *= $units[$matches[2]]; + } + } + + // Height + if (preg_match('~height:\s*(\d+)([a-z]+)~i', $style, $matches)) { + $height = $matches[1]; + // Convert unit to pixel + if (isset($units[$matches[2]])) { + $height *= $units[$matches[2]]; + } + } + } + + // Update width and height + $size[0] = $width; + $size[1] = $height; + + // Return image dimensions + return $size; + } + + /** + * Get the size of a remote image + * + * @param string $uri The URI of the remote image + * @param integer $limit Load first $limit bytes of remote image + * + * @return mixed Returns an array with up to 7 elements + */ + protected function getRemoteImageSize($uri, $limit = -1) + { + // Create temporary file to store data from $uri + $tmp_name = tempnam(sys_get_temp_dir(), uniqid('ris')); + if ($tmp_name === false) { + return false; + } + + // Open temporary file + $tmp = fopen($tmp_name, 'rb'); + + // Check which method we should use to get remote image sizes + $allow_url_fopen = ini_get('allow_url_fopen') ? true : false; + $use_curl = function_exists('curl_version'); + + // Use stream copy + if ($allow_url_fopen) { + $options = []; + if ( $limit > 0 ) { + // Loading number of $limit bytes + $options['http']['header'] = array('Range: bytes=0-' . $limit); + } + + // Create stream context + $context = stream_context_create($options); + @copy($uri, $tmp_name, $context); + + // Use Curl + } elseif ($use_curl) { + // Initialize Curl + $options = array( + CURLOPT_HEADER => false, // Don't return headers + CURLOPT_FOLLOWLOCATION => true, // Follow redirects + CURLOPT_AUT||EFERER => true, // Set referrer on redirect + CURLOPT_CONNECTTIMEOUT => 120, // Timeout on connect + CURLOPT_TIMEOUT => 120, // Timeout on response + CURLOPT_MAXREDIRS => 10, // Stop after 10 redirects + CURLOPT_ENCODING => '', // Handle all encodings + CURLOPT_BINARYTRANSFER => true, // Transfer as binary file + CURLOPT_FILE => $tmp, // Curl file + CURLOPT_URL => $uri, // URI + ); + + $curl = curl_init(); + curl_setopt_array($curl, $options); + + if ( $limit > 0 ) { + // Loading number of $limit + $headers = array('Range: bytes=0-' . $limit); + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + curl_setopt($curl, CURLOPT_RANGE, '0-' . $limit); + + // Abort request when more data is received + curl_setopt($curl, CURLOPT_BUFFERSIZE, 512); // More progress info + curl_setopt($curl, CURLOPT_NOPROGRESS, false); // Monitor progress + curl_setopt($curl, CURLOPT_PROGRESSFUNCTION, + function($download_size, $downloaded, $upload_size, $uploaded) use ($limit) { + // If $downloaded exceeds $limit, returning non-zero breaks + // the connection! + return ( $downloaded > $limit ) ? 1 : 0; + }); + } + + // Execute Curl + curl_exec($curl); + curl_close($curl); + } + + // Close temporary file + fclose($tmp); + + // Retrieve image information + $info = array(0, 0, 'width="0" height="0"'); + if (filesize($tmp_name) > 0) { + $info = @getimagesize($tmp_name); + } + + // Delete temporary file + unlink($tmp_name); + + return $info; + } + + /** + * Load contents into PHP built-in DOMDocument object + * + * Two Really good resources to handle DOMDocument with HTML(5) + * correctly. + * + * @see http://stackoverflow.com/questions/3577641/how-do-you-parse-and-process-html-xml-in-php + * @see http://stackoverflow.com/questions/7997936/how-do-you-format-dom-structures-in-php + * + * @param string $content The content to be loaded into the + * DOMDocument object + * + * @return DOMDocument DOMDocument object of content + */ + protected function loadDOMDocument($content) + { + // Clear previous errors + if (libxml_use_internal_errors(true) === true) { + libxml_clear_errors(); + } + + // Parse content using PHP built-in DOMDocument class + $document = new \DOMDocument('1.0', 'UTF-8'); + + // Encode contents as UTF-8, strip whitespaces & normalize newlines + $content = mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8'); + + // $whitespaces = array( + // '~\R~u' => "\n", // Normalize new line + // '~\>[^\S ]+~s' => '>', // Strip whitespaces after tags, except space + // '~[^\S ]+\<~s' => '<', // Strip whitespaces before tags, except space + // '~(\s)+~s' => '\\1' // Shorten multiple whitespace sequences + // ); + // $content = preg_replace(array_keys($whitespaces), $whitespaces, $content); + + // Parse the HTML using UTF-8 + // The @ before the method call suppresses any warnings that + // loadHTML might throw because of invalid HTML in the page. + @$document->loadHTML($content); + + // Do nothing, if DOM is empty + if (is_null($document->documentElement)) { + return null; + } + + return $document; + } + + /** + * Save contents of PHP built-in DOMDocument object as HTML5 + * + * @param DOMDocument $document DOMDocument object with nodes + * + * @return string The outputted DOM document as HTML(5) + * compliant string + */ + protected function saveDOMDocument($document) + { + // Pretty print output + $document->preserveWhiteSpace = false; + $document->formatOutput = true; + + // Transform DOM document to valid HTML(5) + $content = ''; + $body = $document->getElementsByTagName('body')->item(0); + foreach ($body->childNodes as $node) { + // Expand empty tags (e.g.
to

) + if (($html = $document->saveXML($node, LIBXML_NOEMPTYTAG)) !== false) { + $content .= $html; + } + } + + // Fix formatting for self-closing tags in HTML5 and removing + // encapsulated (uncommented) CDATA blocks in + {% endif %} +{% endblock %} + +{% block header_navigation %} + {% if show_onpage_menu %} +
+ {% else %} + {{ parent() }} + {% endif %} +{% endblock %} + +{% block content %} + {{ page.content }} + {% for module in page.collection() %} +
+ {{ module.content }} + {% endfor %} +{% endblock %} diff --git a/user/themes/usabledashboard/templates/modular/features.html.twig b/user/themes/usabledashboard/templates/modular/features.html.twig new file mode 100644 index 0000000..0f34fc1 --- /dev/null +++ b/user/themes/usabledashboard/templates/modular/features.html.twig @@ -0,0 +1,22 @@ +
+ {{ content }} +
+ {% for feature in page.header.features %} +
+ {% if feature.icon %} + +
+ {% else %} +
+ {% endif %} + {% if feature.header %} +

{{ feature.header }}

+ {% endif %} + {% if feature.text %} +

{{ feature.text }}

+ {% endif %} +
+
+ {% endfor %} +
+
diff --git a/user/themes/usabledashboard/templates/modular/showcase.html.twig b/user/themes/usabledashboard/templates/modular/showcase.html.twig new file mode 100644 index 0000000..cb3aa8e --- /dev/null +++ b/user/themes/usabledashboard/templates/modular/showcase.html.twig @@ -0,0 +1,13 @@ +{% set showcase_image = page.media.images|first.grayscale().contrast(20).brightness(-125).colorize(-35,81,122) %} +{% if showcase_image %} +
+{% else %} +
+{% endif %} + {{ content }} + + {% for button in page.header.buttons %} + {{ button.text }} + {% endfor %} + +
diff --git a/user/themes/usabledashboard/templates/modular/text.html.twig b/user/themes/usabledashboard/templates/modular/text.html.twig new file mode 100644 index 0000000..4b058a5 --- /dev/null +++ b/user/themes/usabledashboard/templates/modular/text.html.twig @@ -0,0 +1,7 @@ +
+ {% set image = page.media.images|first %} + {% if image %} + {{ image.cropResize(400,400).html('','','align-'~page.header.image_align) }} + {% endif %} +{{ content }} +
diff --git a/user/themes/usabledashboard/templates/partials/base.html.twig b/user/themes/usabledashboard/templates/partials/base.html.twig new file mode 100644 index 0000000..56fb63b --- /dev/null +++ b/user/themes/usabledashboard/templates/partials/base.html.twig @@ -0,0 +1,66 @@ +{% set theme_config = attribute(config.themes, config.system.pages.theme) %} + + + + +{% block head %} + + {% if header.title %}{{ header.title|e('html') }} | {% endif %}{{ site.title|e('html') }} + {% include 'partials/metadata.html.twig' %} + + + + + {% block stylesheets %} + {# {% do assets.addCss('theme://css/normalize.css') %} #} + {% do assets.addCss('theme://fonts/ubuntu/ubuntu.css') %} + {% do assets.addCss('theme://css/dashboard.css') %} + {% endblock %} + {{ assets.css() }} + +{% endblock head%} + + +
+ {% block header %} + + {% endblock %} + + {% block body %} +
+ {% block content %}{% endblock %} +
+ {% endblock %} + + {% block footer %} + + {% endblock %} + + {% block bottom %} + + {% endblock %} + +
+ + + {% block javascripts %} + {% do assets.addJs('jquery',101) %} + {% do assets.addJs('theme://js/dist/masonry.pkgd.min.js',100) %} + {% do assets.addJs('theme://js/dist/dashboard.min.js',100) %} + {% endblock %} + {{ assets.js() }} + + diff --git a/user/themes/usabledashboard/templates/partials/blog_item.html.twig b/user/themes/usabledashboard/templates/partials/blog_item.html.twig new file mode 100644 index 0000000..2130536 --- /dev/null +++ b/user/themes/usabledashboard/templates/partials/blog_item.html.twig @@ -0,0 +1,78 @@ +
+ + {% set header_image = page.header.header_image|defined(true) %} + {% set header_image_width = page.header.header_image_width|defined(900) %} + {% set header_image_height = page.header.header_image_height|defined(300) %} + {% set header_image_file = page.header.header_image_file %} + +
+ + {{ page.date|date("d") }} + {{ page.date|date("M") }} + + {% if page.header.link %} +

+ {% if page.header.continue_link is not sameas(false) %} + + {% endif %} + {{ page.title }} +

+ {% else %} +

{{ page.title }}

+ {% endif %} + + {% if page.taxonomy.tag %} + + {% for tag in page.taxonomy.tag %} + {{ tag }} + {% endfor %} + + {% endif %} + {% if header_image %} + {% if header_image_file %} + {% set header_image_media = page.media.images[header_image_file] %} + {% else %} + {% set header_image_media = page.media.images|first %} + {% endif %} + {{ header_image_media.cropZoom(header_image_width, header_image_height).html }} + {% endif %} + +
+ +
+ + {% if page.header.continue_link is sameas(false) %} + {{ page.content }} + {% if not truncate %} + {% set show_prev_next = true %} + {% endif %} + {% elseif truncate and page.summary != page.content %} + {{ page.summary }} +

Continue Reading...

+ {% elseif truncate %} + {% if page.summary != page.content %} + {{ page.content|truncate(550) }} + {% else %} + {{ page.content }} + {% endif %} +

Continue Reading...

+ {% else %} + {{ page.content }} + {% set show_prev_next = true %} + {% endif %} + + {% if show_prev_next %} + +

+ {% if not page.isFirst %} + Next Post + {% endif %} + + {% if not page.isLast %} + Previous Post + {% endif %} +

+ {% endif %} + +
+
diff --git a/user/themes/usabledashboard/templates/partials/dashboard-filters.html.twig b/user/themes/usabledashboard/templates/partials/dashboard-filters.html.twig new file mode 100644 index 0000000..521a44d --- /dev/null +++ b/user/themes/usabledashboard/templates/partials/dashboard-filters.html.twig @@ -0,0 +1,18 @@ +{% set taxlist = taxonomylist.get() %} + + +{% for tax,tags in taxlist %} +
    +

    {{tax}}

    + {% for tag,value in tags %} + {% set active = uri.param(tax) == tag ? 'active' : '' %} +
  • + + {{ tag }} + +
  • + {% endfor %} +
+{% endfor %} diff --git a/user/themes/usabledashboard/templates/partials/metadata.html.twig b/user/themes/usabledashboard/templates/partials/metadata.html.twig new file mode 100644 index 0000000..2f08a0e --- /dev/null +++ b/user/themes/usabledashboard/templates/partials/metadata.html.twig @@ -0,0 +1,3 @@ +{% for meta in page.metadata %} + +{% endfor %} diff --git a/user/themes/usabledashboard/templates/partials/navigation.html.twig b/user/themes/usabledashboard/templates/partials/navigation.html.twig new file mode 100644 index 0000000..2d2d332 --- /dev/null +++ b/user/themes/usabledashboard/templates/partials/navigation.html.twig @@ -0,0 +1,44 @@ +{% macro loop(page) %} + {% for p in page.children.visible %} + {% set current_page = (p.active or p.activeChild) ? 'active' : '' %} +
  • + + {% if p.header.icon %}{% endif %} + {{ p.menu }} + + {% if p.children.visible.count > 0 %} +
      + {{ _self.loop(p) }} +
    + {% endif %} +
  • + {% endfor %} +{% endmacro %} + + + diff --git a/user/themes/usabledashboard/templates/partials/sidebar.html.twig b/user/themes/usabledashboard/templates/partials/sidebar.html.twig new file mode 100644 index 0000000..3712f5c --- /dev/null +++ b/user/themes/usabledashboard/templates/partials/sidebar.html.twig @@ -0,0 +1,42 @@ +{% set feed_url = blog.url == '/' or blog.url == base_url_relative ? (base_url_relative~'/'~blog.slug) : blog.url %} +{% set new_base_url = blog.url == '/' ? '' : blog.url %} + +{% if config.plugins.simplesearch.enabled %} + +{% endif %} +{% if config.plugins.relatedpages.enabled and related_pages|length > 0 %} +

    Related Posts

    + {% include 'partials/relatedpages.html.twig' %} +{% endif %} +{% if config.plugins.random.enabled %} + +{% endif %} + +{% if config.plugins.taxonomylist.enabled %} + +{% endif %} +{% if config.plugins.archives.enabled %} + +{% endif %} +{% if config.plugins.feed.enabled %} + +{% endif %} \ No newline at end of file diff --git a/user/themes/usabledashboard/templates/project.html.twig b/user/themes/usabledashboard/templates/project.html.twig new file mode 100644 index 0000000..a3a40dc --- /dev/null +++ b/user/themes/usabledashboard/templates/project.html.twig @@ -0,0 +1,74 @@ +{% extends 'partials/base.html.twig' %} + +{% block content %} +
    + +

    {{page.header.title}}

    +

    {{page.header.shortdescription}}

    + + +
    + {% for audio in page.media.audios %} +
    + {{ audio.html }} + audio file +
    + {% endfor %} +
    + +
    + {% for video in page.media.videos %} +
    + {{ video.html }} + video file +
    + {% endfor %} +
    + +
    + {% for image in page.media.files %} + {{ image.resize(500).html }} + {% endfor %} + {% for image in page.media.images %} + {{ image.resize(500).html }} + {% endfor %} +
    + +
    + {{ page.content }} +
    + + + + +
    +{% endblock %} diff --git a/user/themes/usabledashboard/templates/snipcart.html.twig b/user/themes/usabledashboard/templates/snipcart.html.twig new file mode 100644 index 0000000..371bc20 --- /dev/null +++ b/user/themes/usabledashboard/templates/snipcart.html.twig @@ -0,0 +1,20 @@ +{% extends 'partials/snipcart_base.html.twig' %} + +{% block content %} + + {% include 'modular/lightslider.html.twig' with {'page': page.find('/slider', true)} %} + + {{ parent() }} + +
    + {{ page.content }} + +
    + {% for child in page.collection() %} + {% if (child.header.unpublished is not defined or (child.header.unpublished == false)) %} + {% include 'partials/snipcart_category_item.html.twig' with {'page': child, 'parent': page} %} + {% endif %} + {% endfor %} +
    +
    +{% endblock %} diff --git a/user/themes/usabledashboard/usabledashboard.php b/user/themes/usabledashboard/usabledashboard.php new file mode 100644 index 0000000..d9f16fb --- /dev/null +++ b/user/themes/usabledashboard/usabledashboard.php @@ -0,0 +1,9 @@ + + +RewriteEngine On + +## Begin RewriteBase +# If you are getting 404 errors on subpages, you may have to uncomment the RewriteBase entry +# You should change the '/' to your appropriate subfolder. For example if you have +# your Grav install at the root of your site '/' should work, else it might be something +# along the lines of: RewriteBase / +## + +# RewriteBase / + +## End - RewriteBase + +## Begin - Exploits +# If you experience problems on your site block out the operations listed below +# This attempts to block the most common type of exploit `attempts` to Grav +# +# Block out any script trying to base64_encode data within the URL. +RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR] +# Block out any script that includes a