From 230a2b594e3f4370db22b5a7dd404a5c9eb665ba Mon Sep 17 00:00:00 2001 From: Djamil Legato Date: Sat, 2 Aug 2014 12:11:33 -0700 Subject: [PATCH] Source --- bin/grav | 21 + composer.json | 25 + index.php | 42 + system/autoload.php | 10 + system/blueprints/assets.yaml | 5 + system/blueprints/page.yaml | 28 + system/blueprints/site.yaml | 42 + system/blueprints/system.yaml | 275 ++ system/config/assets.yaml | 58 + system/config/site.yaml | 13 + system/config/system.yaml | 39 + system/defines.php | 42 + system/images/assets/thumb-doc.png | Bin 0 -> 2202 bytes system/images/assets/thumb-gif.png | Bin 0 -> 2809 bytes system/images/assets/thumb-gz.png | Bin 0 -> 1956 bytes system/images/assets/thumb-html.png | Bin 0 -> 1797 bytes system/images/assets/thumb-jpeg.png | Bin 0 -> 3072 bytes system/images/assets/thumb-jpg.png | Bin 0 -> 2974 bytes system/images/assets/thumb-m4v.png | Bin 0 -> 2259 bytes system/images/assets/thumb-mov.png | Bin 0 -> 2409 bytes system/images/assets/thumb-mp4.png | Bin 0 -> 1978 bytes system/images/assets/thumb-pdf.png | Bin 0 -> 1853 bytes system/images/assets/thumb-png.png | Bin 0 -> 3115 bytes system/images/assets/thumb-swf.png | Bin 0 -> 2317 bytes system/images/assets/thumb-txt.png | Bin 0 -> 1853 bytes system/images/assets/thumb-zip.png | Bin 0 -> 1885 bytes system/src/Grav/Common/Cache.php | 145 ++ system/src/Grav/Common/Config.php | 256 ++ system/src/Grav/Common/Data/Blueprint.php | 473 ++++ system/src/Grav/Common/Data/Blueprints.php | 81 + system/src/Grav/Common/Data/Data.php | 235 ++ system/src/Grav/Common/Data/DataInterface.php | 69 + system/src/Grav/Common/Data/Validation.php | 603 +++++ .../Grav/Common/Filesystem/File/Config.php | 129 + .../Grav/Common/Filesystem/File/General.php | 352 +++ .../src/Grav/Common/Filesystem/File/Json.php | 54 + .../src/Grav/Common/Filesystem/File/Log.php | 69 + .../Grav/Common/Filesystem/File/Markdown.php | 120 + .../src/Grav/Common/Filesystem/File/Yaml.php | 61 + .../Grav/Common/Filesystem/FileInterface.php | 100 + system/src/Grav/Common/Filesystem/Folder.php | 221 ++ system/src/Grav/Common/Getters.php | 150 ++ system/src/Grav/Common/Grav.php | 214 ++ system/src/Grav/Common/Inflector.php | 370 +++ system/src/Grav/Common/Iterator.php | 394 +++ system/src/Grav/Common/Page/Asset.php | 321 +++ system/src/Grav/Common/Page/Assets.php | 201 ++ system/src/Grav/Common/Page/Collection.php | 187 ++ system/src/Grav/Common/Page/Page.php | 1588 +++++++++++ system/src/Grav/Common/Page/Pages.php | 547 ++++ system/src/Grav/Common/Plugin.php | 26 + system/src/Grav/Common/Plugins.php | 110 + system/src/Grav/Common/Registry.php | 98 + system/src/Grav/Common/Session/Message.php | 91 + system/src/Grav/Common/Session/Session.php | 245 ++ system/src/Grav/Common/Taxonomy.php | 99 + system/src/Grav/Common/Theme.php | 6 + system/src/Grav/Common/Themes.php | 101 + system/src/Grav/Common/Twig.php | 241 ++ system/src/Grav/Common/TwigExtension.php | 232 ++ system/src/Grav/Common/Uri.php | 287 ++ .../src/Grav/Common/User/Authentication.php | 46 + system/src/Grav/Common/User/User.php | 45 + system/src/Grav/Common/Utils.php | 142 + system/src/Grav/Console/InstallCommand.php | 283 ++ system/src/Grav/Console/PackageCommand.php | 167 ++ user/config/site.yaml | 6 + user/config/system.yaml | 31 + user/pages/01.home/default.md | 39 + user/plugins/error/blueprints.yaml | 16 + user/plugins/error/error.php | 46 + user/plugins/error/error.yaml | 6 + user/plugins/error/pages/error.md | 8 + user/plugins/error/templates/error.html.twig | 3 + user/plugins/problems/problems.css | 61 + user/plugins/problems/problems.php | 173 ++ user/plugins/problems/problems.yaml | 2 + .../problems/templates/problems.html.twig | 55 + user/themes/antimatter/antimatter.php | 9 + user/themes/antimatter/antimatter.yaml | 2 + user/themes/antimatter/blueprints.yaml | 26 + .../antimatter/blueprints/asset/file.yaml | 70 + user/themes/antimatter/blueprints/blog.yaml | 67 + .../themes/antimatter/blueprints/default.yaml | 163 ++ user/themes/antimatter/blueprints/form.yaml | 2 + user/themes/antimatter/blueprints/item.yaml | 40 + .../themes/antimatter/blueprints/modular.yaml | 27 + .../antimatter/css-compiled/nucleus.css | 621 +++++ .../antimatter/css-compiled/nucleus.css.map | 7 + .../antimatter/css-compiled/particles.css | 98 + .../antimatter/css-compiled/particles.css.map | 7 + .../antimatter/css-compiled/platform.css | 3 + .../antimatter/css-compiled/platform.css.map | 7 + .../antimatter/css-compiled/template.css | 2318 +++++++++++++++++ .../antimatter/css-compiled/template.css.map | 6 + .../vendor/fontawesome/font-awesome.css | 1558 +++++++++++ .../vendor/fontawesome/font-awesome.css.map | 7 + .../antimatter/css/featherlight.min.css | 8 + user/themes/antimatter/css/nucleus-ie10.css | 9 + user/themes/antimatter/css/nucleus-ie9.css | 62 + user/themes/antimatter/css/prism.css | 121 + .../antimatter/css/pure-0.5.0/grids-min.css | 15 + .../fonts/fontawesome/FontAwesome.otf | Bin 0 -> 62856 bytes .../fonts/fontawesome/fontawesome-webfont.eot | Bin 0 -> 72449 bytes .../fonts/fontawesome/fontawesome-webfont.svg | 504 ++++ .../fonts/fontawesome/fontawesome-webfont.ttf | Bin 0 -> 141564 bytes .../fontawesome/fontawesome-webfont.woff | Bin 0 -> 83760 bytes user/themes/antimatter/images/favicon.png | Bin 0 -> 535 bytes user/themes/antimatter/images/logo.png | Bin 0 -> 3190 bytes user/themes/antimatter/js/featherlight.min.js | 8 + .../antimatter/js/html5shiv-printshiv.min.js | 4 + user/themes/antimatter/js/jquery-2.1.1.min.js | 4 + .../antimatter/js/modernizr.custom.71422.js | 4 + user/themes/antimatter/js/nova-theme.js | 23 + user/themes/antimatter/js/pushy.min.js | 4 + user/themes/antimatter/scss.sh | 2 + .../scss/configuration/nucleus/_base.scss | 14 + .../configuration/nucleus/_breakpoints.scss | 16 + .../scss/configuration/nucleus/_core.scss | 2 + .../scss/configuration/nucleus/_layout.scss | 8 + .../scss/configuration/nucleus/_nav.scss | 3 + .../configuration/nucleus/_typography.scss | 14 + .../scss/configuration/template/_base.scss | 11 + .../scss/configuration/template/_bullets.scss | 5 + .../scss/configuration/template/_colors.scss | 62 + .../configuration/template/_typography.scss | 7 + .../configuration/template/_variables.scss | 13 + user/themes/antimatter/scss/nucleus.scss | 27 + .../themes/antimatter/scss/nucleus/_core.scss | 217 ++ .../themes/antimatter/scss/nucleus/_flex.scss | 187 ++ .../antimatter/scss/nucleus/_forms.scss | 63 + .../antimatter/scss/nucleus/_typography.scss | 86 + .../scss/nucleus/functions/_base.scss | 2 + .../scss/nucleus/functions/_direction.scss | 37 + .../scss/nucleus/functions/_range.scss | 13 + .../antimatter/scss/nucleus/mixins/_base.scss | 2 + .../scss/nucleus/mixins/_breakpoints.scss | 27 + .../scss/nucleus/mixins/_utilities.scss | 30 + .../scss/nucleus/particles/_align-text.scss | 46 + .../scss/nucleus/particles/_visibility.scss | 0 user/themes/antimatter/scss/particles.scss | 24 + user/themes/antimatter/scss/platform.scss | 21 + .../scss/platform/joomla/_core.scss | 0 user/themes/antimatter/scss/template.scss | 65 + .../antimatter/scss/template/_blog.scss | 148 ++ .../antimatter/scss/template/_bullets.scss | 60 + .../antimatter/scss/template/_buttons.scss | 9 + .../antimatter/scss/template/_core.scss | 48 + .../antimatter/scss/template/_custom.scss | 1 + .../antimatter/scss/template/_errors.scss | 17 + .../antimatter/scss/template/_extensions.scss | 22 + .../antimatter/scss/template/_fonts.scss | 3 + .../antimatter/scss/template/_footer.scss | 42 + .../antimatter/scss/template/_forms.scss | 43 + .../antimatter/scss/template/_header.scss | 161 ++ .../antimatter/scss/template/_panel.scss | 87 + .../antimatter/scss/template/_tables.scss | 14 + .../antimatter/scss/template/_typography.scss | 196 ++ .../scss/template/modular/_all.scss | 9 + .../scss/template/modular/_features.scss | 87 + .../scss/template/modular/_showcase.scss | 66 + .../scss/template/modular/_text.scss | 24 + .../scss/template/modules/_base.scss | 2 + .../scss/template/modules/_buttons.scss | 19 + .../bourbon/_bourbon-deprecated-upcoming.scss | 8 + .../scss/vendor/bourbon/_bourbon.scss | 77 + .../scss/vendor/bourbon/addons/_button.scss | 374 +++ .../scss/vendor/bourbon/addons/_clearfix.scss | 23 + .../bourbon/addons/_directional-values.scss | 111 + .../scss/vendor/bourbon/addons/_ellipsis.scss | 7 + .../vendor/bourbon/addons/_font-family.scss | 5 + .../vendor/bourbon/addons/_hide-text.scss | 10 + .../bourbon/addons/_html5-input-types.scss | 86 + .../scss/vendor/bourbon/addons/_position.scss | 32 + .../scss/vendor/bourbon/addons/_prefixer.scss | 45 + .../scss/vendor/bourbon/addons/_rem.scss | 33 + .../vendor/bourbon/addons/_retina-image.scss | 31 + .../scss/vendor/bourbon/addons/_size.scss | 16 + .../bourbon/addons/_timing-functions.scss | 32 + .../scss/vendor/bourbon/addons/_triangle.scss | 83 + .../vendor/bourbon/addons/_word-wrap.scss | 8 + .../scss/vendor/bourbon/css3/_animation.scss | 52 + .../scss/vendor/bourbon/css3/_appearance.scss | 3 + .../bourbon/css3/_backface-visibility.scss | 6 + .../bourbon/css3/_background-image.scss | 42 + .../scss/vendor/bourbon/css3/_background.scss | 55 + .../vendor/bourbon/css3/_border-image.scss | 59 + .../vendor/bourbon/css3/_border-radius.scss | 22 + .../scss/vendor/bourbon/css3/_box-sizing.scss | 4 + .../scss/vendor/bourbon/css3/_calc.scss | 4 + .../scss/vendor/bourbon/css3/_columns.scss | 47 + .../scss/vendor/bourbon/css3/_filter.scss | 5 + .../scss/vendor/bourbon/css3/_flex-box.scss | 321 +++ .../scss/vendor/bourbon/css3/_font-face.scss | 23 + .../bourbon/css3/_font-feature-settings.scss | 10 + .../bourbon/css3/_hidpi-media-query.scss | 10 + .../scss/vendor/bourbon/css3/_hyphens.scss | 4 + .../vendor/bourbon/css3/_image-rendering.scss | 14 + .../scss/vendor/bourbon/css3/_keyframes.scss | 35 + .../vendor/bourbon/css3/_linear-gradient.scss | 38 + .../vendor/bourbon/css3/_perspective.scss | 8 + .../vendor/bourbon/css3/_placeholder.scss | 8 + .../vendor/bourbon/css3/_radial-gradient.scss | 39 + .../scss/vendor/bourbon/css3/_transform.scss | 15 + .../scss/vendor/bourbon/css3/_transition.scss | 77 + .../vendor/bourbon/css3/_user-select.scss | 3 + .../vendor/bourbon/functions/_assign.scss | 11 + .../bourbon/functions/_color-lightness.scss | 13 + .../vendor/bourbon/functions/_flex-grid.scss | 39 + .../bourbon/functions/_golden-ratio.scss | 3 + .../vendor/bourbon/functions/_grid-width.scss | 13 + .../bourbon/functions/_modular-scale.scss | 66 + .../vendor/bourbon/functions/_px-to-em.scss | 13 + .../vendor/bourbon/functions/_px-to-rem.scss | 15 + .../bourbon/functions/_strip-units.scss | 5 + .../vendor/bourbon/functions/_tint-shade.scss | 9 + .../functions/_transition-property-name.scss | 22 + .../vendor/bourbon/functions/_unpack.scss | 17 + .../bourbon/helpers/_convert-units.scss | 15 + .../helpers/_gradient-positions-parser.scss | 13 + .../scss/vendor/bourbon/helpers/_is-num.scss | 8 + .../bourbon/helpers/_linear-angle-parser.scss | 25 + .../helpers/_linear-gradient-parser.scss | 41 + .../helpers/_linear-positions-parser.scss | 61 + .../helpers/_linear-side-corner-parser.scss | 31 + .../bourbon/helpers/_radial-arg-parser.scss | 69 + .../helpers/_radial-gradient-parser.scss | 50 + .../helpers/_radial-positions-parser.scss | 18 + .../bourbon/helpers/_render-gradients.scss | 26 + .../bourbon/helpers/_shape-size-stripper.scss | 10 + .../vendor/bourbon/helpers/_str-to-num.scss | 50 + .../vendor/bourbon/settings/_prefixer.scss | 6 + .../vendor/bourbon/settings/_px-to-em.scss | 1 + .../vendor/color-schemer/_color-schemer.scss | 31 + .../color-schemer/color-schemer/_cmyk.scss | 14 + .../color-schemer/_color-adjustments.scss | 30 + .../color-schemer/_color-schemer.scss | 208 ++ .../color-schemer/_colorblind.scss | 29 + .../color-schemer/_comparison.scss | 15 + .../color-schemer/_equalize.scss | 5 + .../color-schemer/_harmonize.scss | 59 + .../color-schemer/_interpolation.scss | 34 + .../color-schemer/color-schemer/_mix.scss | 40 + .../color-schemer/color-schemer/_mixins.scss | 29 + .../color-schemer/color-schemer/_ryb.scss | 76 + .../color-schemer/_tint-shade.scss | 9 + .../vendor/fontawesome/_bordered-pulled.scss | 16 + .../scss/vendor/fontawesome/_core.scss | 12 + .../scss/vendor/fontawesome/_fixed-width.scss | 6 + .../scss/vendor/fontawesome/_icons.scss | 506 ++++ .../scss/vendor/fontawesome/_larger.scss | 13 + .../scss/vendor/fontawesome/_list.scss | 19 + .../scss/vendor/fontawesome/_mixins.scss | 20 + .../scss/vendor/fontawesome/_path.scss | 14 + .../vendor/fontawesome/_rotated-flipped.scss | 9 + .../scss/vendor/fontawesome/_spinning.scss | 30 + .../scss/vendor/fontawesome/_stacked.scss | 20 + .../scss/vendor/fontawesome/_variables.scss | 515 ++++ .../scss/vendor/fontawesome/font-awesome.scss | 17 + .../antimatter/templates/blog.html.twig | 39 + .../antimatter/templates/default.html.twig | 5 + .../antimatter/templates/error.html.twig | 12 + .../antimatter/templates/form.html.twig | 8 + .../antimatter/templates/formdata.html.twig | 12 + .../antimatter/templates/forms/data.html.twig | 3 + .../antimatter/templates/forms/data.txt.twig | 3 + .../antimatter/templates/forms/form.html.twig | 18 + .../antimatter/templates/item.html.twig | 24 + .../antimatter/templates/modular.html.twig | 8 + .../templates/modular/features.html.twig | 22 + .../templates/modular/showcase.html.twig | 13 + .../templates/modular/text.html.twig | 7 + .../templates/partials/archives.html.twig | 7 + .../templates/partials/base.html.twig | 90 + .../templates/partials/blog_item.html.twig | 33 + .../templates/partials/navigation.html.twig | 9 + .../templates/partials/sidebar.html.twig | 17 + user/themes/antimatter/thumbnail.jpg | Bin 0 -> 24301 bytes vendor/autoload.php | 7 + vendor/composer/ClassLoader.php | 379 +++ vendor/composer/autoload_classmap.php | 20 + vendor/composer/autoload_files.php | 11 + vendor/composer/autoload_namespaces.php | 16 + vendor/composer/autoload_psr4.php | 9 + vendor/composer/autoload_real.php | 55 + vendor/composer/installed.json | 476 ++++ vendor/doctrine/cache/LICENSE | 19 + vendor/doctrine/cache/README.md | 14 + .../lib/Doctrine/Common/Cache/ApcCache.php | 98 + .../lib/Doctrine/Common/Cache/ArrayCache.php | 93 + .../cache/lib/Doctrine/Common/Cache/Cache.php | 111 + .../Doctrine/Common/Cache/CacheProvider.php | 241 ++ .../Doctrine/Common/Cache/CouchbaseCache.php | 121 + .../lib/Doctrine/Common/Cache/FileCache.php | 158 ++ .../Doctrine/Common/Cache/FilesystemCache.php | 113 + .../Doctrine/Common/Cache/MemcacheCache.php | 121 + .../Doctrine/Common/Cache/MemcachedCache.php | 124 + .../Doctrine/Common/Cache/MongoDBCache.php | 191 ++ .../Doctrine/Common/Cache/PhpFileCache.php | 107 + .../lib/Doctrine/Common/Cache/RedisCache.php | 131 + .../lib/Doctrine/Common/Cache/RiakCache.php | 250 ++ .../lib/Doctrine/Common/Cache/Version.php | 25 + .../Doctrine/Common/Cache/WinCacheCache.php | 91 + .../lib/Doctrine/Common/Cache/XcacheCache.php | 109 + .../Doctrine/Common/Cache/ZendDataCache.php | 83 + vendor/erusev/parsedown/LICENSE.txt | 20 + vendor/erusev/parsedown/Parsedown.php | 1402 ++++++++++ vendor/erusev/parsedown/README.md | 41 + vendor/gregwar/cache/Gregwar/Cache/Cache.php | 295 +++ .../cache/Gregwar/Cache/GarbageCollect.php | 86 + vendor/gregwar/cache/Gregwar/Cache/LICENSE | 19 + vendor/gregwar/cache/Gregwar/Cache/README.md | 162 ++ .../gregwar/cache/Gregwar/Cache/autoload.php | 16 + .../image/Gregwar/Image/Adapter/Adapter.php | 58 + .../Image/Adapter/AdapterInterface.php | 378 +++ .../image/Gregwar/Image/Adapter/Common.php | 275 ++ .../image/Gregwar/Image/Adapter/GD.php | 516 ++++ .../image/Gregwar/Image/Adapter/Imagick.php | 382 +++ .../Image/Exceptions/GenerationError.php | 16 + .../image/Gregwar/Image/GarbageCollect.php | 83 + vendor/gregwar/image/Gregwar/Image/Image.php | 717 +++++ .../image/Gregwar/Image/ImageColor.php | 93 + vendor/gregwar/image/Gregwar/Image/LICENSE | 19 + vendor/gregwar/image/Gregwar/Image/README.md | 271 ++ .../image/Gregwar/Image/Source/Create.php | 38 + .../image/Gregwar/Image/Source/Data.php | 26 + .../image/Gregwar/Image/Source/File.php | 63 + .../image/Gregwar/Image/Source/Resource.php | 21 + .../image/Gregwar/Image/Source/Source.php | 34 + .../gregwar/image/Gregwar/Image/autoload.php | 21 + .../image/Gregwar/Image/images/error.jpg | Bin 0 -> 1108 bytes vendor/ircmaxell/password-compat/LICENSE.md | 7 + vendor/ircmaxell/password-compat/README.md | 75 + .../password-compat/lib/password.php | 222 ++ .../Symfony/Component/Console/Application.php | 1156 ++++++++ .../Symfony/Component/Console/CHANGELOG.md | 56 + .../Component/Console/Command/Command.php | 652 +++++ .../Component/Console/Command/HelpCommand.php | 91 + .../Component/Console/Command/ListCommand.php | 95 + .../Component/Console/ConsoleEvents.php | 55 + .../Descriptor/ApplicationDescription.php | 153 ++ .../Console/Descriptor/Descriptor.php | 119 + .../Descriptor/DescriptorInterface.php | 31 + .../Console/Descriptor/JsonDescriptor.php | 165 ++ .../Console/Descriptor/MarkdownDescriptor.php | 139 + .../Console/Descriptor/TextDescriptor.php | 229 ++ .../Console/Descriptor/XmlDescriptor.php | 264 ++ .../Console/Event/ConsoleCommandEvent.php | 21 + .../Component/Console/Event/ConsoleEvent.php | 67 + .../Console/Event/ConsoleExceptionEvent.php | 67 + .../Console/Event/ConsoleTerminateEvent.php | 58 + .../Console/Formatter/OutputFormatter.php | 236 ++ .../Formatter/OutputFormatterInterface.php | 83 + .../Formatter/OutputFormatterStyle.php | 228 ++ .../OutputFormatterStyleInterface.php | 72 + .../Formatter/OutputFormatterStyleStack.php | 121 + .../Console/Helper/DescriptorHelper.php | 96 + .../Component/Console/Helper/DialogHelper.php | 476 ++++ .../Console/Helper/FormatterHelper.php | 80 + .../Component/Console/Helper/Helper.php | 121 + .../Console/Helper/HelperInterface.php | 49 + .../Component/Console/Helper/HelperSet.php | 108 + .../Console/Helper/InputAwareHelper.php | 33 + .../Component/Console/Helper/ProgressBar.php | 559 ++++ .../Console/Helper/ProgressHelper.php | 457 ++++ .../Console/Helper/QuestionHelper.php | 411 +++ .../Component/Console/Helper/Table.php | 409 +++ .../Component/Console/Helper/TableHelper.php | 262 ++ .../Console/Helper/TableSeparator.php | 21 + .../Component/Console/Helper/TableStyle.php | 251 ++ .../Component/Console/Input/ArgvInput.php | 351 +++ .../Component/Console/Input/ArrayInput.php | 209 ++ .../Symfony/Component/Console/Input/Input.php | 226 ++ .../Component/Console/Input/InputArgument.php | 132 + .../Console/Input/InputAwareInterface.php | 28 + .../Console/Input/InputDefinition.php | 458 ++++ .../Console/Input/InputInterface.php | 152 ++ .../Component/Console/Input/InputOption.php | 212 ++ .../Component/Console/Input/StringInput.php | 83 + .../console/Symfony/Component/Console/LICENSE | 19 + .../Console/Logger/ConsoleLogger.php | 116 + .../Console/Output/BufferedOutput.php | 48 + .../Console/Output/ConsoleOutput.php | 113 + .../Console/Output/ConsoleOutputInterface.php | 35 + .../Component/Console/Output/NullOutput.php | 113 + .../Component/Console/Output/Output.php | 165 ++ .../Console/Output/OutputInterface.php | 113 + .../Component/Console/Output/StreamOutput.php | 103 + .../Console/Question/ChoiceQuestion.php | 138 + .../Console/Question/ConfirmationQuestion.php | 44 + .../Component/Console/Question/Question.php | 238 ++ .../Symfony/Component/Console/README.md | 63 + .../Console/Resources/bin/hiddeninput.exe | Bin 0 -> 9216 bytes .../Symfony/Component/Console/Shell.php | 228 ++ .../Console/Tester/ApplicationTester.php | 128 + .../Console/Tester/CommandTester.php | 132 + .../yaml/Symfony/Component/Yaml/CHANGELOG.md | 8 + .../yaml/Symfony/Component/Yaml/Dumper.php | 73 + .../yaml/Symfony/Component/Yaml/Escaper.php | 89 + .../Yaml/Exception/DumpException.php | 23 + .../Yaml/Exception/ExceptionInterface.php | 23 + .../Yaml/Exception/ParseException.php | 148 ++ .../Yaml/Exception/RuntimeException.php | 23 + .../yaml/Symfony/Component/Yaml/Inline.php | 487 ++++ .../yaml/Symfony/Component/Yaml/LICENSE | 19 + .../yaml/Symfony/Component/Yaml/Parser.php | 673 +++++ .../yaml/Symfony/Component/Yaml/README.md | 19 + .../yaml/Symfony/Component/Yaml/Unescaper.php | 141 + .../yaml/Symfony/Component/Yaml/Yaml.php | 100 + vendor/tracy/tracy/license.md | 55 + vendor/tracy/tracy/readme.md | 226 ++ vendor/tracy/tracy/src/Tracy/Bar.php | 112 + vendor/tracy/tracy/src/Tracy/BlueScreen.php | 178 ++ vendor/tracy/tracy/src/Tracy/Debugger.php | 503 ++++ .../tracy/tracy/src/Tracy/DefaultBarPanel.php | 57 + vendor/tracy/tracy/src/Tracy/Dumper.php | 349 +++ vendor/tracy/tracy/src/Tracy/FireLogger.php | 182 ++ vendor/tracy/tracy/src/Tracy/Helpers.php | 155 ++ vendor/tracy/tracy/src/Tracy/IBarPanel.php | 33 + vendor/tracy/tracy/src/Tracy/ILogger.php | 27 + vendor/tracy/tracy/src/Tracy/Logger.php | 160 ++ .../tracy/tracy/src/Tracy/OutputDebugger.php | 73 + .../tracy/tracy/src/Tracy/templates/bar.css | 318 +++ .../src/Tracy/templates/bar.dumps.panel.phtml | 47 + .../src/Tracy/templates/bar.dumps.tab.phtml | 18 + .../Tracy/templates/bar.errors.panel.phtml | 26 + .../src/Tracy/templates/bar.errors.tab.phtml | 19 + vendor/tracy/tracy/src/Tracy/templates/bar.js | 293 +++ .../src/Tracy/templates/bar.memory.tab.phtml | 15 + .../tracy/tracy/src/Tracy/templates/bar.phtml | 82 + .../src/Tracy/templates/bar.time.tab.phtml | 15 + .../tracy/src/Tracy/templates/bluescreen.css | 298 +++ .../src/Tracy/templates/bluescreen.phtml | 407 +++ .../tracy/src/Tracy/templates/dumper.css | 73 + .../tracy/tracy/src/Tracy/templates/dumper.js | 54 + .../tracy/src/Tracy/templates/error.phtml | 36 + .../tracy/tracy/src/Tracy/templates/tracyQ.js | 294 +++ vendor/tracy/tracy/src/shortcuts.php | 20 + vendor/tracy/tracy/src/tracy.php | 20 + .../tracy/tools/create-phar/create-phar.php | 26 + .../tracy/tools/open-in-editor/install.cmd | 8 + .../tracy/tools/open-in-editor/open-editor.js | 47 + vendor/twig/twig/CHANGELOG | 688 +++++ vendor/twig/twig/LICENSE | 31 + vendor/twig/twig/README.rst | 15 + vendor/twig/twig/lib/Twig/Autoloader.php | 48 + vendor/twig/twig/lib/Twig/Compiler.php | 270 ++ .../twig/twig/lib/Twig/CompilerInterface.php | 36 + vendor/twig/twig/lib/Twig/Environment.php | 1254 +++++++++ vendor/twig/twig/lib/Twig/Error.php | 248 ++ vendor/twig/twig/lib/Twig/Error/Loader.php | 31 + vendor/twig/twig/lib/Twig/Error/Runtime.php | 20 + vendor/twig/twig/lib/Twig/Error/Syntax.php | 20 + .../twig/lib/Twig/ExistsLoaderInterface.php | 29 + .../twig/twig/lib/Twig/ExpressionParser.php | 598 +++++ vendor/twig/twig/lib/Twig/Extension.php | 93 + vendor/twig/twig/lib/Twig/Extension/Core.php | 1462 +++++++++++ vendor/twig/twig/lib/Twig/Extension/Debug.php | 71 + .../twig/twig/lib/Twig/Extension/Escaper.php | 107 + .../twig/lib/Twig/Extension/Optimizer.php | 35 + .../twig/twig/lib/Twig/Extension/Sandbox.php | 112 + .../twig/twig/lib/Twig/Extension/Staging.php | 113 + .../twig/lib/Twig/Extension/StringLoader.php | 64 + .../twig/twig/lib/Twig/ExtensionInterface.php | 83 + vendor/twig/twig/lib/Twig/Filter.php | 81 + vendor/twig/twig/lib/Twig/Filter/Function.php | 37 + vendor/twig/twig/lib/Twig/Filter/Method.php | 39 + vendor/twig/twig/lib/Twig/Filter/Node.php | 39 + .../twig/lib/Twig/FilterCallableInterface.php | 23 + vendor/twig/twig/lib/Twig/FilterInterface.php | 42 + vendor/twig/twig/lib/Twig/Function.php | 71 + .../twig/twig/lib/Twig/Function/Function.php | 38 + vendor/twig/twig/lib/Twig/Function/Method.php | 40 + vendor/twig/twig/lib/Twig/Function/Node.php | 39 + .../lib/Twig/FunctionCallableInterface.php | 23 + .../twig/twig/lib/Twig/FunctionInterface.php | 39 + vendor/twig/twig/lib/Twig/Lexer.php | 409 +++ vendor/twig/twig/lib/Twig/LexerInterface.php | 32 + vendor/twig/twig/lib/Twig/Loader/Array.php | 95 + vendor/twig/twig/lib/Twig/Loader/Chain.php | 138 + .../twig/twig/lib/Twig/Loader/Filesystem.php | 236 ++ vendor/twig/twig/lib/Twig/Loader/String.php | 59 + vendor/twig/twig/lib/Twig/LoaderInterface.php | 52 + vendor/twig/twig/lib/Twig/Markup.php | 37 + vendor/twig/twig/lib/Twig/Node.php | 226 ++ vendor/twig/twig/lib/Twig/Node/AutoEscape.php | 39 + vendor/twig/twig/lib/Twig/Node/Block.php | 44 + .../twig/lib/Twig/Node/BlockReference.php | 37 + vendor/twig/twig/lib/Twig/Node/Body.php | 19 + vendor/twig/twig/lib/Twig/Node/Do.php | 38 + vendor/twig/twig/lib/Twig/Node/Embed.php | 38 + vendor/twig/twig/lib/Twig/Node/Expression.php | 20 + .../twig/lib/Twig/Node/Expression/Array.php | 86 + .../lib/Twig/Node/Expression/AssignName.php | 28 + .../twig/lib/Twig/Node/Expression/Binary.php | 40 + .../lib/Twig/Node/Expression/Binary/Add.php | 18 + .../lib/Twig/Node/Expression/Binary/And.php | 18 + .../Node/Expression/Binary/BitwiseAnd.php | 18 + .../Twig/Node/Expression/Binary/BitwiseOr.php | 18 + .../Node/Expression/Binary/BitwiseXor.php | 18 + .../Twig/Node/Expression/Binary/Concat.php | 18 + .../lib/Twig/Node/Expression/Binary/Div.php | 18 + .../Twig/Node/Expression/Binary/EndsWith.php | 30 + .../lib/Twig/Node/Expression/Binary/Equal.php | 17 + .../Twig/Node/Expression/Binary/FloorDiv.php | 29 + .../Twig/Node/Expression/Binary/Greater.php | 17 + .../Node/Expression/Binary/GreaterEqual.php | 17 + .../lib/Twig/Node/Expression/Binary/In.php | 33 + .../lib/Twig/Node/Expression/Binary/Less.php | 17 + .../Twig/Node/Expression/Binary/LessEqual.php | 17 + .../Twig/Node/Expression/Binary/Matches.php | 28 + .../lib/Twig/Node/Expression/Binary/Mod.php | 18 + .../lib/Twig/Node/Expression/Binary/Mul.php | 18 + .../Twig/Node/Expression/Binary/NotEqual.php | 17 + .../lib/Twig/Node/Expression/Binary/NotIn.php | 33 + .../lib/Twig/Node/Expression/Binary/Or.php | 18 + .../lib/Twig/Node/Expression/Binary/Power.php | 33 + .../lib/Twig/Node/Expression/Binary/Range.php | 33 + .../Node/Expression/Binary/StartsWith.php | 28 + .../lib/Twig/Node/Expression/Binary/Sub.php | 18 + .../Twig/Node/Expression/BlockReference.php | 51 + .../twig/lib/Twig/Node/Expression/Call.php | 176 ++ .../lib/Twig/Node/Expression/Conditional.php | 31 + .../lib/Twig/Node/Expression/Constant.php | 23 + .../Node/Expression/ExtensionReference.php | 33 + .../twig/lib/Twig/Node/Expression/Filter.php | 36 + .../Twig/Node/Expression/Filter/Default.php | 43 + .../lib/Twig/Node/Expression/Function.php | 35 + .../twig/lib/Twig/Node/Expression/GetAttr.php | 53 + .../lib/Twig/Node/Expression/MethodCall.php | 41 + .../twig/lib/Twig/Node/Expression/Name.php | 88 + .../twig/lib/Twig/Node/Expression/Parent.php | 47 + .../lib/Twig/Node/Expression/TempName.php | 26 + .../twig/lib/Twig/Node/Expression/Test.php | 32 + .../Twig/Node/Expression/Test/Constant.php | 46 + .../lib/Twig/Node/Expression/Test/Defined.php | 54 + .../Twig/Node/Expression/Test/Divisibleby.php | 33 + .../lib/Twig/Node/Expression/Test/Even.php | 32 + .../lib/Twig/Node/Expression/Test/Null.php | 31 + .../lib/Twig/Node/Expression/Test/Odd.php | 32 + .../lib/Twig/Node/Expression/Test/Sameas.php | 29 + .../twig/lib/Twig/Node/Expression/Unary.php | 30 + .../lib/Twig/Node/Expression/Unary/Neg.php | 18 + .../lib/Twig/Node/Expression/Unary/Not.php | 18 + .../lib/Twig/Node/Expression/Unary/Pos.php | 18 + vendor/twig/twig/lib/Twig/Node/Flush.php | 36 + vendor/twig/twig/lib/Twig/Node/For.php | 112 + vendor/twig/twig/lib/Twig/Node/ForLoop.php | 55 + vendor/twig/twig/lib/Twig/Node/If.php | 66 + vendor/twig/twig/lib/Twig/Node/Import.php | 50 + vendor/twig/twig/lib/Twig/Node/Include.php | 85 + vendor/twig/twig/lib/Twig/Node/Macro.php | 96 + vendor/twig/twig/lib/Twig/Node/Module.php | 383 +++ vendor/twig/twig/lib/Twig/Node/Print.php | 39 + vendor/twig/twig/lib/Twig/Node/Sandbox.php | 47 + .../twig/lib/Twig/Node/SandboxedModule.php | 60 + .../twig/lib/Twig/Node/SandboxedPrint.php | 59 + vendor/twig/twig/lib/Twig/Node/Set.php | 101 + vendor/twig/twig/lib/Twig/Node/SetTemp.php | 35 + vendor/twig/twig/lib/Twig/Node/Spaceless.php | 40 + vendor/twig/twig/lib/Twig/Node/Text.php | 39 + vendor/twig/twig/lib/Twig/NodeInterface.php | 31 + .../twig/lib/Twig/NodeOutputInterface.php | 19 + vendor/twig/twig/lib/Twig/NodeTraverser.php | 88 + .../twig/lib/Twig/NodeVisitor/Escaper.php | 167 ++ .../twig/lib/Twig/NodeVisitor/Optimizer.php | 246 ++ .../lib/Twig/NodeVisitor/SafeAnalysis.php | 139 + .../twig/lib/Twig/NodeVisitor/Sandbox.php | 92 + .../twig/lib/Twig/NodeVisitorInterface.php | 47 + vendor/twig/twig/lib/Twig/Parser.php | 390 +++ vendor/twig/twig/lib/Twig/ParserInterface.php | 31 + .../twig/lib/Twig/Sandbox/SecurityError.php | 19 + .../twig/lib/Twig/Sandbox/SecurityPolicy.php | 119 + .../Twig/Sandbox/SecurityPolicyInterface.php | 24 + vendor/twig/twig/lib/Twig/SimpleFilter.php | 94 + vendor/twig/twig/lib/Twig/SimpleFunction.php | 84 + vendor/twig/twig/lib/Twig/SimpleTest.php | 46 + vendor/twig/twig/lib/Twig/Template.php | 485 ++++ .../twig/twig/lib/Twig/TemplateInterface.php | 48 + vendor/twig/twig/lib/Twig/Test.php | 34 + vendor/twig/twig/lib/Twig/Test/Function.php | 35 + .../lib/Twig/Test/IntegrationTestCase.php | 154 ++ vendor/twig/twig/lib/Twig/Test/Method.php | 37 + vendor/twig/twig/lib/Twig/Test/Node.php | 37 + .../twig/twig/lib/Twig/Test/NodeTestCase.php | 58 + .../twig/lib/Twig/TestCallableInterface.php | 21 + vendor/twig/twig/lib/Twig/TestInterface.php | 26 + vendor/twig/twig/lib/Twig/Token.php | 216 ++ vendor/twig/twig/lib/Twig/TokenParser.php | 33 + .../twig/lib/Twig/TokenParser/AutoEscape.php | 89 + .../twig/twig/lib/Twig/TokenParser/Block.php | 81 + vendor/twig/twig/lib/Twig/TokenParser/Do.php | 42 + .../twig/twig/lib/Twig/TokenParser/Embed.php | 66 + .../twig/lib/Twig/TokenParser/Extends.php | 52 + .../twig/twig/lib/Twig/TokenParser/Filter.php | 61 + .../twig/twig/lib/Twig/TokenParser/Flush.php | 42 + vendor/twig/twig/lib/Twig/TokenParser/For.php | 135 + .../twig/twig/lib/Twig/TokenParser/From.php | 70 + vendor/twig/twig/lib/Twig/TokenParser/If.php | 94 + .../twig/twig/lib/Twig/TokenParser/Import.php | 49 + .../twig/lib/Twig/TokenParser/Include.php | 75 + .../twig/twig/lib/Twig/TokenParser/Macro.php | 68 + .../twig/lib/Twig/TokenParser/Sandbox.php | 68 + vendor/twig/twig/lib/Twig/TokenParser/Set.php | 83 + .../twig/lib/Twig/TokenParser/Spaceless.php | 59 + vendor/twig/twig/lib/Twig/TokenParser/Use.php | 76 + .../twig/twig/lib/Twig/TokenParserBroker.php | 136 + .../lib/Twig/TokenParserBrokerInterface.php | 45 + .../twig/lib/Twig/TokenParserInterface.php | 43 + vendor/twig/twig/lib/Twig/TokenStream.php | 156 ++ 610 files changed, 63359 insertions(+) create mode 100755 bin/grav create mode 100644 composer.json create mode 100644 index.php create mode 100644 system/autoload.php create mode 100644 system/blueprints/assets.yaml create mode 100644 system/blueprints/page.yaml create mode 100644 system/blueprints/site.yaml create mode 100644 system/blueprints/system.yaml create mode 100644 system/config/assets.yaml create mode 100644 system/config/site.yaml create mode 100644 system/config/system.yaml create mode 100644 system/defines.php create mode 100644 system/images/assets/thumb-doc.png create mode 100644 system/images/assets/thumb-gif.png create mode 100644 system/images/assets/thumb-gz.png create mode 100644 system/images/assets/thumb-html.png create mode 100644 system/images/assets/thumb-jpeg.png create mode 100644 system/images/assets/thumb-jpg.png create mode 100644 system/images/assets/thumb-m4v.png create mode 100644 system/images/assets/thumb-mov.png create mode 100644 system/images/assets/thumb-mp4.png create mode 100644 system/images/assets/thumb-pdf.png create mode 100644 system/images/assets/thumb-png.png create mode 100644 system/images/assets/thumb-swf.png create mode 100644 system/images/assets/thumb-txt.png create mode 100644 system/images/assets/thumb-zip.png create mode 100644 system/src/Grav/Common/Cache.php create mode 100644 system/src/Grav/Common/Config.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/Filesystem/File/Config.php create mode 100644 system/src/Grav/Common/Filesystem/File/General.php create mode 100644 system/src/Grav/Common/Filesystem/File/Json.php create mode 100644 system/src/Grav/Common/Filesystem/File/Log.php create mode 100644 system/src/Grav/Common/Filesystem/File/Markdown.php create mode 100644 system/src/Grav/Common/Filesystem/File/Yaml.php create mode 100644 system/src/Grav/Common/Filesystem/FileInterface.php create mode 100644 system/src/Grav/Common/Filesystem/Folder.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/Inflector.php create mode 100644 system/src/Grav/Common/Iterator.php create mode 100644 system/src/Grav/Common/Page/Asset.php create mode 100644 system/src/Grav/Common/Page/Assets.php create mode 100644 system/src/Grav/Common/Page/Collection.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/Plugin.php create mode 100644 system/src/Grav/Common/Plugins.php create mode 100644 system/src/Grav/Common/Registry.php create mode 100644 system/src/Grav/Common/Session/Message.php create mode 100644 system/src/Grav/Common/Session/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.php create mode 100644 system/src/Grav/Common/TwigExtension.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/User.php create mode 100644 system/src/Grav/Common/Utils.php create mode 100644 system/src/Grav/Console/InstallCommand.php create mode 100644 system/src/Grav/Console/PackageCommand.php create mode 100644 user/config/site.yaml create mode 100644 user/config/system.yaml create mode 100644 user/pages/01.home/default.md create mode 100644 user/plugins/error/blueprints.yaml create mode 100644 user/plugins/error/error.php create mode 100644 user/plugins/error/error.yaml create mode 100644 user/plugins/error/pages/error.md create mode 100644 user/plugins/error/templates/error.html.twig create mode 100644 user/plugins/problems/problems.css create mode 100644 user/plugins/problems/problems.php create mode 100644 user/plugins/problems/problems.yaml create mode 100644 user/plugins/problems/templates/problems.html.twig create mode 100644 user/themes/antimatter/antimatter.php create mode 100644 user/themes/antimatter/antimatter.yaml create mode 100644 user/themes/antimatter/blueprints.yaml create mode 100644 user/themes/antimatter/blueprints/asset/file.yaml create mode 100644 user/themes/antimatter/blueprints/blog.yaml create mode 100644 user/themes/antimatter/blueprints/default.yaml create mode 100644 user/themes/antimatter/blueprints/form.yaml create mode 100644 user/themes/antimatter/blueprints/item.yaml create mode 100644 user/themes/antimatter/blueprints/modular.yaml create mode 100644 user/themes/antimatter/css-compiled/nucleus.css create mode 100644 user/themes/antimatter/css-compiled/nucleus.css.map create mode 100644 user/themes/antimatter/css-compiled/particles.css create mode 100644 user/themes/antimatter/css-compiled/particles.css.map create mode 100644 user/themes/antimatter/css-compiled/platform.css create mode 100644 user/themes/antimatter/css-compiled/platform.css.map create mode 100644 user/themes/antimatter/css-compiled/template.css create mode 100644 user/themes/antimatter/css-compiled/template.css.map create mode 100644 user/themes/antimatter/css-compiled/vendor/fontawesome/font-awesome.css create mode 100644 user/themes/antimatter/css-compiled/vendor/fontawesome/font-awesome.css.map create mode 100644 user/themes/antimatter/css/featherlight.min.css create mode 100644 user/themes/antimatter/css/nucleus-ie10.css create mode 100644 user/themes/antimatter/css/nucleus-ie9.css create mode 100644 user/themes/antimatter/css/prism.css create mode 100644 user/themes/antimatter/css/pure-0.5.0/grids-min.css create mode 100644 user/themes/antimatter/fonts/fontawesome/FontAwesome.otf create mode 100644 user/themes/antimatter/fonts/fontawesome/fontawesome-webfont.eot create mode 100644 user/themes/antimatter/fonts/fontawesome/fontawesome-webfont.svg create mode 100644 user/themes/antimatter/fonts/fontawesome/fontawesome-webfont.ttf create mode 100644 user/themes/antimatter/fonts/fontawesome/fontawesome-webfont.woff create mode 100644 user/themes/antimatter/images/favicon.png create mode 100644 user/themes/antimatter/images/logo.png create mode 100644 user/themes/antimatter/js/featherlight.min.js create mode 100644 user/themes/antimatter/js/html5shiv-printshiv.min.js create mode 100644 user/themes/antimatter/js/jquery-2.1.1.min.js create mode 100644 user/themes/antimatter/js/modernizr.custom.71422.js create mode 100644 user/themes/antimatter/js/nova-theme.js create mode 100644 user/themes/antimatter/js/pushy.min.js create mode 100644 user/themes/antimatter/scss.sh create mode 100644 user/themes/antimatter/scss/configuration/nucleus/_base.scss create mode 100644 user/themes/antimatter/scss/configuration/nucleus/_breakpoints.scss create mode 100644 user/themes/antimatter/scss/configuration/nucleus/_core.scss create mode 100644 user/themes/antimatter/scss/configuration/nucleus/_layout.scss create mode 100644 user/themes/antimatter/scss/configuration/nucleus/_nav.scss create mode 100644 user/themes/antimatter/scss/configuration/nucleus/_typography.scss create mode 100644 user/themes/antimatter/scss/configuration/template/_base.scss create mode 100644 user/themes/antimatter/scss/configuration/template/_bullets.scss create mode 100644 user/themes/antimatter/scss/configuration/template/_colors.scss create mode 100644 user/themes/antimatter/scss/configuration/template/_typography.scss create mode 100644 user/themes/antimatter/scss/configuration/template/_variables.scss create mode 100644 user/themes/antimatter/scss/nucleus.scss create mode 100644 user/themes/antimatter/scss/nucleus/_core.scss create mode 100644 user/themes/antimatter/scss/nucleus/_flex.scss create mode 100644 user/themes/antimatter/scss/nucleus/_forms.scss create mode 100644 user/themes/antimatter/scss/nucleus/_typography.scss create mode 100644 user/themes/antimatter/scss/nucleus/functions/_base.scss create mode 100644 user/themes/antimatter/scss/nucleus/functions/_direction.scss create mode 100644 user/themes/antimatter/scss/nucleus/functions/_range.scss create mode 100644 user/themes/antimatter/scss/nucleus/mixins/_base.scss create mode 100644 user/themes/antimatter/scss/nucleus/mixins/_breakpoints.scss create mode 100644 user/themes/antimatter/scss/nucleus/mixins/_utilities.scss create mode 100644 user/themes/antimatter/scss/nucleus/particles/_align-text.scss create mode 100644 user/themes/antimatter/scss/nucleus/particles/_visibility.scss create mode 100644 user/themes/antimatter/scss/particles.scss create mode 100644 user/themes/antimatter/scss/platform.scss create mode 100644 user/themes/antimatter/scss/platform/joomla/_core.scss create mode 100644 user/themes/antimatter/scss/template.scss create mode 100644 user/themes/antimatter/scss/template/_blog.scss create mode 100644 user/themes/antimatter/scss/template/_bullets.scss create mode 100644 user/themes/antimatter/scss/template/_buttons.scss create mode 100644 user/themes/antimatter/scss/template/_core.scss create mode 100644 user/themes/antimatter/scss/template/_custom.scss create mode 100644 user/themes/antimatter/scss/template/_errors.scss create mode 100644 user/themes/antimatter/scss/template/_extensions.scss create mode 100644 user/themes/antimatter/scss/template/_fonts.scss create mode 100644 user/themes/antimatter/scss/template/_footer.scss create mode 100644 user/themes/antimatter/scss/template/_forms.scss create mode 100644 user/themes/antimatter/scss/template/_header.scss create mode 100644 user/themes/antimatter/scss/template/_panel.scss create mode 100644 user/themes/antimatter/scss/template/_tables.scss create mode 100644 user/themes/antimatter/scss/template/_typography.scss create mode 100644 user/themes/antimatter/scss/template/modular/_all.scss create mode 100644 user/themes/antimatter/scss/template/modular/_features.scss create mode 100644 user/themes/antimatter/scss/template/modular/_showcase.scss create mode 100644 user/themes/antimatter/scss/template/modular/_text.scss create mode 100644 user/themes/antimatter/scss/template/modules/_base.scss create mode 100644 user/themes/antimatter/scss/template/modules/_buttons.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/_bourbon-deprecated-upcoming.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/_bourbon.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/addons/_button.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/addons/_clearfix.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/addons/_directional-values.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/addons/_ellipsis.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/addons/_font-family.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/addons/_hide-text.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/addons/_html5-input-types.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/addons/_position.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/addons/_prefixer.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/addons/_rem.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/addons/_retina-image.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/addons/_size.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/addons/_timing-functions.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/addons/_triangle.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/addons/_word-wrap.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_animation.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_appearance.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_backface-visibility.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_background-image.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_background.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_border-image.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_border-radius.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_box-sizing.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_calc.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_columns.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_filter.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_flex-box.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_font-face.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_font-feature-settings.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_hidpi-media-query.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_hyphens.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_image-rendering.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_keyframes.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_linear-gradient.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_perspective.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_placeholder.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_radial-gradient.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_transform.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_transition.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/css3/_user-select.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/functions/_assign.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/functions/_color-lightness.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/functions/_flex-grid.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/functions/_golden-ratio.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/functions/_grid-width.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/functions/_modular-scale.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/functions/_px-to-em.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/functions/_px-to-rem.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/functions/_strip-units.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/functions/_tint-shade.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/functions/_transition-property-name.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/functions/_unpack.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/helpers/_convert-units.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/helpers/_gradient-positions-parser.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/helpers/_is-num.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/helpers/_linear-angle-parser.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/helpers/_linear-gradient-parser.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/helpers/_linear-positions-parser.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/helpers/_linear-side-corner-parser.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/helpers/_radial-arg-parser.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/helpers/_radial-gradient-parser.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/helpers/_radial-positions-parser.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/helpers/_render-gradients.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/helpers/_shape-size-stripper.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/helpers/_str-to-num.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/settings/_prefixer.scss create mode 100644 user/themes/antimatter/scss/vendor/bourbon/settings/_px-to-em.scss create mode 100644 user/themes/antimatter/scss/vendor/color-schemer/_color-schemer.scss create mode 100644 user/themes/antimatter/scss/vendor/color-schemer/color-schemer/_cmyk.scss create mode 100644 user/themes/antimatter/scss/vendor/color-schemer/color-schemer/_color-adjustments.scss create mode 100644 user/themes/antimatter/scss/vendor/color-schemer/color-schemer/_color-schemer.scss create mode 100644 user/themes/antimatter/scss/vendor/color-schemer/color-schemer/_colorblind.scss create mode 100644 user/themes/antimatter/scss/vendor/color-schemer/color-schemer/_comparison.scss create mode 100644 user/themes/antimatter/scss/vendor/color-schemer/color-schemer/_equalize.scss create mode 100644 user/themes/antimatter/scss/vendor/color-schemer/color-schemer/_harmonize.scss create mode 100644 user/themes/antimatter/scss/vendor/color-schemer/color-schemer/_interpolation.scss create mode 100644 user/themes/antimatter/scss/vendor/color-schemer/color-schemer/_mix.scss create mode 100644 user/themes/antimatter/scss/vendor/color-schemer/color-schemer/_mixins.scss create mode 100644 user/themes/antimatter/scss/vendor/color-schemer/color-schemer/_ryb.scss create mode 100644 user/themes/antimatter/scss/vendor/color-schemer/color-schemer/_tint-shade.scss create mode 100644 user/themes/antimatter/scss/vendor/fontawesome/_bordered-pulled.scss create mode 100644 user/themes/antimatter/scss/vendor/fontawesome/_core.scss create mode 100644 user/themes/antimatter/scss/vendor/fontawesome/_fixed-width.scss create mode 100644 user/themes/antimatter/scss/vendor/fontawesome/_icons.scss create mode 100644 user/themes/antimatter/scss/vendor/fontawesome/_larger.scss create mode 100644 user/themes/antimatter/scss/vendor/fontawesome/_list.scss create mode 100644 user/themes/antimatter/scss/vendor/fontawesome/_mixins.scss create mode 100644 user/themes/antimatter/scss/vendor/fontawesome/_path.scss create mode 100644 user/themes/antimatter/scss/vendor/fontawesome/_rotated-flipped.scss create mode 100644 user/themes/antimatter/scss/vendor/fontawesome/_spinning.scss create mode 100644 user/themes/antimatter/scss/vendor/fontawesome/_stacked.scss create mode 100644 user/themes/antimatter/scss/vendor/fontawesome/_variables.scss create mode 100644 user/themes/antimatter/scss/vendor/fontawesome/font-awesome.scss create mode 100644 user/themes/antimatter/templates/blog.html.twig create mode 100644 user/themes/antimatter/templates/default.html.twig create mode 100644 user/themes/antimatter/templates/error.html.twig create mode 100644 user/themes/antimatter/templates/form.html.twig create mode 100644 user/themes/antimatter/templates/formdata.html.twig create mode 100644 user/themes/antimatter/templates/forms/data.html.twig create mode 100644 user/themes/antimatter/templates/forms/data.txt.twig create mode 100644 user/themes/antimatter/templates/forms/form.html.twig create mode 100644 user/themes/antimatter/templates/item.html.twig create mode 100644 user/themes/antimatter/templates/modular.html.twig create mode 100644 user/themes/antimatter/templates/modular/features.html.twig create mode 100644 user/themes/antimatter/templates/modular/showcase.html.twig create mode 100644 user/themes/antimatter/templates/modular/text.html.twig create mode 100644 user/themes/antimatter/templates/partials/archives.html.twig create mode 100644 user/themes/antimatter/templates/partials/base.html.twig create mode 100644 user/themes/antimatter/templates/partials/blog_item.html.twig create mode 100644 user/themes/antimatter/templates/partials/navigation.html.twig create mode 100644 user/themes/antimatter/templates/partials/sidebar.html.twig create mode 100644 user/themes/antimatter/thumbnail.jpg create mode 100644 vendor/autoload.php create mode 100644 vendor/composer/ClassLoader.php create mode 100644 vendor/composer/autoload_classmap.php create mode 100644 vendor/composer/autoload_files.php create mode 100644 vendor/composer/autoload_namespaces.php create mode 100644 vendor/composer/autoload_psr4.php create mode 100644 vendor/composer/autoload_real.php create mode 100644 vendor/composer/installed.json create mode 100644 vendor/doctrine/cache/LICENSE create mode 100644 vendor/doctrine/cache/README.md create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/MongoDBCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/RedisCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/RiakCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/Version.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/WinCacheCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php create mode 100644 vendor/erusev/parsedown/LICENSE.txt create mode 100644 vendor/erusev/parsedown/Parsedown.php create mode 100644 vendor/erusev/parsedown/README.md create mode 100644 vendor/gregwar/cache/Gregwar/Cache/Cache.php create mode 100644 vendor/gregwar/cache/Gregwar/Cache/GarbageCollect.php create mode 100644 vendor/gregwar/cache/Gregwar/Cache/LICENSE create mode 100644 vendor/gregwar/cache/Gregwar/Cache/README.md create mode 100644 vendor/gregwar/cache/Gregwar/Cache/autoload.php create mode 100644 vendor/gregwar/image/Gregwar/Image/Adapter/Adapter.php create mode 100644 vendor/gregwar/image/Gregwar/Image/Adapter/AdapterInterface.php create mode 100644 vendor/gregwar/image/Gregwar/Image/Adapter/Common.php create mode 100644 vendor/gregwar/image/Gregwar/Image/Adapter/GD.php create mode 100644 vendor/gregwar/image/Gregwar/Image/Adapter/Imagick.php create mode 100644 vendor/gregwar/image/Gregwar/Image/Exceptions/GenerationError.php create mode 100644 vendor/gregwar/image/Gregwar/Image/GarbageCollect.php create mode 100644 vendor/gregwar/image/Gregwar/Image/Image.php create mode 100644 vendor/gregwar/image/Gregwar/Image/ImageColor.php create mode 100644 vendor/gregwar/image/Gregwar/Image/LICENSE create mode 100644 vendor/gregwar/image/Gregwar/Image/README.md create mode 100644 vendor/gregwar/image/Gregwar/Image/Source/Create.php create mode 100644 vendor/gregwar/image/Gregwar/Image/Source/Data.php create mode 100644 vendor/gregwar/image/Gregwar/Image/Source/File.php create mode 100644 vendor/gregwar/image/Gregwar/Image/Source/Resource.php create mode 100644 vendor/gregwar/image/Gregwar/Image/Source/Source.php create mode 100644 vendor/gregwar/image/Gregwar/Image/autoload.php create mode 100644 vendor/gregwar/image/Gregwar/Image/images/error.jpg create mode 100644 vendor/ircmaxell/password-compat/LICENSE.md create mode 100644 vendor/ircmaxell/password-compat/README.md create mode 100644 vendor/ircmaxell/password-compat/lib/password.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Application.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/CHANGELOG.md create mode 100644 vendor/symfony/console/Symfony/Component/Console/Command/Command.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Command/HelpCommand.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Command/ListCommand.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/ConsoleEvents.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Descriptor/ApplicationDescription.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Descriptor/Descriptor.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Descriptor/DescriptorInterface.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Descriptor/JsonDescriptor.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Descriptor/TextDescriptor.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Descriptor/XmlDescriptor.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Event/ConsoleCommandEvent.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Event/ConsoleEvent.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Event/ConsoleExceptionEvent.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Event/ConsoleTerminateEvent.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatter.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterInterface.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyle.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyleInterface.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyleStack.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Helper/DescriptorHelper.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Helper/DialogHelper.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Helper/FormatterHelper.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Helper/Helper.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Helper/HelperInterface.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Helper/HelperSet.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Helper/InputAwareHelper.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Helper/ProgressBar.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Helper/ProgressHelper.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Helper/QuestionHelper.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Helper/Table.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Helper/TableHelper.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Helper/TableSeparator.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Helper/TableStyle.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Input/ArgvInput.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Input/ArrayInput.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Input/Input.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Input/InputArgument.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Input/InputAwareInterface.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Input/InputDefinition.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Input/InputInterface.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Input/InputOption.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Input/StringInput.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/LICENSE create mode 100644 vendor/symfony/console/Symfony/Component/Console/Logger/ConsoleLogger.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Output/BufferedOutput.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Output/ConsoleOutput.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Output/ConsoleOutputInterface.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Output/NullOutput.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Output/Output.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Output/OutputInterface.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Output/StreamOutput.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Question/ChoiceQuestion.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Question/ConfirmationQuestion.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Question/Question.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/README.md create mode 100644 vendor/symfony/console/Symfony/Component/Console/Resources/bin/hiddeninput.exe create mode 100644 vendor/symfony/console/Symfony/Component/Console/Shell.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Tester/ApplicationTester.php create mode 100644 vendor/symfony/console/Symfony/Component/Console/Tester/CommandTester.php create mode 100644 vendor/symfony/yaml/Symfony/Component/Yaml/CHANGELOG.md create mode 100644 vendor/symfony/yaml/Symfony/Component/Yaml/Dumper.php create mode 100644 vendor/symfony/yaml/Symfony/Component/Yaml/Escaper.php create mode 100644 vendor/symfony/yaml/Symfony/Component/Yaml/Exception/DumpException.php create mode 100644 vendor/symfony/yaml/Symfony/Component/Yaml/Exception/ExceptionInterface.php create mode 100644 vendor/symfony/yaml/Symfony/Component/Yaml/Exception/ParseException.php create mode 100644 vendor/symfony/yaml/Symfony/Component/Yaml/Exception/RuntimeException.php create mode 100644 vendor/symfony/yaml/Symfony/Component/Yaml/Inline.php create mode 100644 vendor/symfony/yaml/Symfony/Component/Yaml/LICENSE create mode 100644 vendor/symfony/yaml/Symfony/Component/Yaml/Parser.php create mode 100644 vendor/symfony/yaml/Symfony/Component/Yaml/README.md create mode 100644 vendor/symfony/yaml/Symfony/Component/Yaml/Unescaper.php create mode 100644 vendor/symfony/yaml/Symfony/Component/Yaml/Yaml.php create mode 100644 vendor/tracy/tracy/license.md create mode 100644 vendor/tracy/tracy/readme.md create mode 100644 vendor/tracy/tracy/src/Tracy/Bar.php create mode 100644 vendor/tracy/tracy/src/Tracy/BlueScreen.php create mode 100644 vendor/tracy/tracy/src/Tracy/Debugger.php create mode 100644 vendor/tracy/tracy/src/Tracy/DefaultBarPanel.php create mode 100644 vendor/tracy/tracy/src/Tracy/Dumper.php create mode 100644 vendor/tracy/tracy/src/Tracy/FireLogger.php create mode 100644 vendor/tracy/tracy/src/Tracy/Helpers.php create mode 100644 vendor/tracy/tracy/src/Tracy/IBarPanel.php create mode 100644 vendor/tracy/tracy/src/Tracy/ILogger.php create mode 100644 vendor/tracy/tracy/src/Tracy/Logger.php create mode 100644 vendor/tracy/tracy/src/Tracy/OutputDebugger.php create mode 100644 vendor/tracy/tracy/src/Tracy/templates/bar.css create mode 100644 vendor/tracy/tracy/src/Tracy/templates/bar.dumps.panel.phtml create mode 100644 vendor/tracy/tracy/src/Tracy/templates/bar.dumps.tab.phtml create mode 100644 vendor/tracy/tracy/src/Tracy/templates/bar.errors.panel.phtml create mode 100644 vendor/tracy/tracy/src/Tracy/templates/bar.errors.tab.phtml create mode 100644 vendor/tracy/tracy/src/Tracy/templates/bar.js create mode 100644 vendor/tracy/tracy/src/Tracy/templates/bar.memory.tab.phtml create mode 100644 vendor/tracy/tracy/src/Tracy/templates/bar.phtml create mode 100644 vendor/tracy/tracy/src/Tracy/templates/bar.time.tab.phtml create mode 100644 vendor/tracy/tracy/src/Tracy/templates/bluescreen.css create mode 100644 vendor/tracy/tracy/src/Tracy/templates/bluescreen.phtml create mode 100644 vendor/tracy/tracy/src/Tracy/templates/dumper.css create mode 100644 vendor/tracy/tracy/src/Tracy/templates/dumper.js create mode 100644 vendor/tracy/tracy/src/Tracy/templates/error.phtml create mode 100644 vendor/tracy/tracy/src/Tracy/templates/tracyQ.js create mode 100644 vendor/tracy/tracy/src/shortcuts.php create mode 100644 vendor/tracy/tracy/src/tracy.php create mode 100644 vendor/tracy/tracy/tools/create-phar/create-phar.php create mode 100644 vendor/tracy/tracy/tools/open-in-editor/install.cmd create mode 100644 vendor/tracy/tracy/tools/open-in-editor/open-editor.js create mode 100644 vendor/twig/twig/CHANGELOG create mode 100644 vendor/twig/twig/LICENSE create mode 100644 vendor/twig/twig/README.rst create mode 100644 vendor/twig/twig/lib/Twig/Autoloader.php create mode 100644 vendor/twig/twig/lib/Twig/Compiler.php create mode 100644 vendor/twig/twig/lib/Twig/CompilerInterface.php create mode 100644 vendor/twig/twig/lib/Twig/Environment.php create mode 100644 vendor/twig/twig/lib/Twig/Error.php create mode 100644 vendor/twig/twig/lib/Twig/Error/Loader.php create mode 100644 vendor/twig/twig/lib/Twig/Error/Runtime.php create mode 100644 vendor/twig/twig/lib/Twig/Error/Syntax.php create mode 100644 vendor/twig/twig/lib/Twig/ExistsLoaderInterface.php create mode 100644 vendor/twig/twig/lib/Twig/ExpressionParser.php create mode 100644 vendor/twig/twig/lib/Twig/Extension.php create mode 100644 vendor/twig/twig/lib/Twig/Extension/Core.php create mode 100644 vendor/twig/twig/lib/Twig/Extension/Debug.php create mode 100644 vendor/twig/twig/lib/Twig/Extension/Escaper.php create mode 100644 vendor/twig/twig/lib/Twig/Extension/Optimizer.php create mode 100644 vendor/twig/twig/lib/Twig/Extension/Sandbox.php create mode 100644 vendor/twig/twig/lib/Twig/Extension/Staging.php create mode 100644 vendor/twig/twig/lib/Twig/Extension/StringLoader.php create mode 100644 vendor/twig/twig/lib/Twig/ExtensionInterface.php create mode 100644 vendor/twig/twig/lib/Twig/Filter.php create mode 100644 vendor/twig/twig/lib/Twig/Filter/Function.php create mode 100644 vendor/twig/twig/lib/Twig/Filter/Method.php create mode 100644 vendor/twig/twig/lib/Twig/Filter/Node.php create mode 100644 vendor/twig/twig/lib/Twig/FilterCallableInterface.php create mode 100644 vendor/twig/twig/lib/Twig/FilterInterface.php create mode 100644 vendor/twig/twig/lib/Twig/Function.php create mode 100644 vendor/twig/twig/lib/Twig/Function/Function.php create mode 100644 vendor/twig/twig/lib/Twig/Function/Method.php create mode 100644 vendor/twig/twig/lib/Twig/Function/Node.php create mode 100644 vendor/twig/twig/lib/Twig/FunctionCallableInterface.php create mode 100644 vendor/twig/twig/lib/Twig/FunctionInterface.php create mode 100644 vendor/twig/twig/lib/Twig/Lexer.php create mode 100644 vendor/twig/twig/lib/Twig/LexerInterface.php create mode 100644 vendor/twig/twig/lib/Twig/Loader/Array.php create mode 100644 vendor/twig/twig/lib/Twig/Loader/Chain.php create mode 100644 vendor/twig/twig/lib/Twig/Loader/Filesystem.php create mode 100644 vendor/twig/twig/lib/Twig/Loader/String.php create mode 100644 vendor/twig/twig/lib/Twig/LoaderInterface.php create mode 100644 vendor/twig/twig/lib/Twig/Markup.php create mode 100644 vendor/twig/twig/lib/Twig/Node.php create mode 100644 vendor/twig/twig/lib/Twig/Node/AutoEscape.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Block.php create mode 100644 vendor/twig/twig/lib/Twig/Node/BlockReference.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Body.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Do.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Embed.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Array.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/AssignName.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/Add.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/And.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseAnd.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseOr.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseXor.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/Concat.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/Div.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/EndsWith.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/Equal.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/FloorDiv.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/Greater.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/GreaterEqual.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/In.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/Less.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/LessEqual.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/Matches.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/Mod.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/Mul.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/NotEqual.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/NotIn.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/Or.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/Power.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/Range.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/StartsWith.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Binary/Sub.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/BlockReference.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Call.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Conditional.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Constant.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/ExtensionReference.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Filter.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Filter/Default.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Function.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/GetAttr.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/MethodCall.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Name.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Parent.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/TempName.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Test.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Test/Constant.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Test/Defined.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Test/Divisibleby.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Test/Even.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Test/Null.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Test/Odd.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Test/Sameas.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Unary.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Unary/Neg.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Unary/Not.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Expression/Unary/Pos.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Flush.php create mode 100644 vendor/twig/twig/lib/Twig/Node/For.php create mode 100644 vendor/twig/twig/lib/Twig/Node/ForLoop.php create mode 100644 vendor/twig/twig/lib/Twig/Node/If.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Import.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Include.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Macro.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Module.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Print.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Sandbox.php create mode 100644 vendor/twig/twig/lib/Twig/Node/SandboxedModule.php create mode 100644 vendor/twig/twig/lib/Twig/Node/SandboxedPrint.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Set.php create mode 100644 vendor/twig/twig/lib/Twig/Node/SetTemp.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Spaceless.php create mode 100644 vendor/twig/twig/lib/Twig/Node/Text.php create mode 100644 vendor/twig/twig/lib/Twig/NodeInterface.php create mode 100644 vendor/twig/twig/lib/Twig/NodeOutputInterface.php create mode 100644 vendor/twig/twig/lib/Twig/NodeTraverser.php create mode 100644 vendor/twig/twig/lib/Twig/NodeVisitor/Escaper.php create mode 100644 vendor/twig/twig/lib/Twig/NodeVisitor/Optimizer.php create mode 100644 vendor/twig/twig/lib/Twig/NodeVisitor/SafeAnalysis.php create mode 100644 vendor/twig/twig/lib/Twig/NodeVisitor/Sandbox.php create mode 100644 vendor/twig/twig/lib/Twig/NodeVisitorInterface.php create mode 100644 vendor/twig/twig/lib/Twig/Parser.php create mode 100644 vendor/twig/twig/lib/Twig/ParserInterface.php create mode 100644 vendor/twig/twig/lib/Twig/Sandbox/SecurityError.php create mode 100644 vendor/twig/twig/lib/Twig/Sandbox/SecurityPolicy.php create mode 100644 vendor/twig/twig/lib/Twig/Sandbox/SecurityPolicyInterface.php create mode 100644 vendor/twig/twig/lib/Twig/SimpleFilter.php create mode 100644 vendor/twig/twig/lib/Twig/SimpleFunction.php create mode 100644 vendor/twig/twig/lib/Twig/SimpleTest.php create mode 100644 vendor/twig/twig/lib/Twig/Template.php create mode 100644 vendor/twig/twig/lib/Twig/TemplateInterface.php create mode 100644 vendor/twig/twig/lib/Twig/Test.php create mode 100644 vendor/twig/twig/lib/Twig/Test/Function.php create mode 100644 vendor/twig/twig/lib/Twig/Test/IntegrationTestCase.php create mode 100644 vendor/twig/twig/lib/Twig/Test/Method.php create mode 100644 vendor/twig/twig/lib/Twig/Test/Node.php create mode 100644 vendor/twig/twig/lib/Twig/Test/NodeTestCase.php create mode 100644 vendor/twig/twig/lib/Twig/TestCallableInterface.php create mode 100644 vendor/twig/twig/lib/Twig/TestInterface.php create mode 100644 vendor/twig/twig/lib/Twig/Token.php create mode 100644 vendor/twig/twig/lib/Twig/TokenParser.php create mode 100644 vendor/twig/twig/lib/Twig/TokenParser/AutoEscape.php create mode 100644 vendor/twig/twig/lib/Twig/TokenParser/Block.php create mode 100644 vendor/twig/twig/lib/Twig/TokenParser/Do.php create mode 100644 vendor/twig/twig/lib/Twig/TokenParser/Embed.php create mode 100644 vendor/twig/twig/lib/Twig/TokenParser/Extends.php create mode 100644 vendor/twig/twig/lib/Twig/TokenParser/Filter.php create mode 100644 vendor/twig/twig/lib/Twig/TokenParser/Flush.php create mode 100644 vendor/twig/twig/lib/Twig/TokenParser/For.php create mode 100644 vendor/twig/twig/lib/Twig/TokenParser/From.php create mode 100644 vendor/twig/twig/lib/Twig/TokenParser/If.php create mode 100644 vendor/twig/twig/lib/Twig/TokenParser/Import.php create mode 100644 vendor/twig/twig/lib/Twig/TokenParser/Include.php create mode 100644 vendor/twig/twig/lib/Twig/TokenParser/Macro.php create mode 100644 vendor/twig/twig/lib/Twig/TokenParser/Sandbox.php create mode 100644 vendor/twig/twig/lib/Twig/TokenParser/Set.php create mode 100644 vendor/twig/twig/lib/Twig/TokenParser/Spaceless.php create mode 100644 vendor/twig/twig/lib/Twig/TokenParser/Use.php create mode 100644 vendor/twig/twig/lib/Twig/TokenParserBroker.php create mode 100644 vendor/twig/twig/lib/Twig/TokenParserBrokerInterface.php create mode 100644 vendor/twig/twig/lib/Twig/TokenParserInterface.php create mode 100644 vendor/twig/twig/lib/Twig/TokenStream.php diff --git a/bin/grav b/bin/grav new file mode 100755 index 000000000..1bc34ca15 --- /dev/null +++ b/bin/grav @@ -0,0 +1,21 @@ +#!/usr/bin/env php +addCommands(array( + new Grav\Console\InstallCommand(), + new Grav\Console\PackageCommand(), +)); +$app->run(); diff --git a/composer.json b/composer.json new file mode 100644 index 000000000..c937e27a8 --- /dev/null +++ b/composer.json @@ -0,0 +1,25 @@ +{ + "name": "rhuk/grav", + "type": "library", + "description": "Grav is a powerful flat CMS influenced by Pico, Stacey, Kirby and others...", + "keywords": ["cms"], + "homepage": "http://getgrav.org", + "license": "MIT", + "authors": [ + { + "name": "Andy Miller", + "email": "rhuk@getgrav.org" + } + ], + "require": { + "php": ">=5.3.10", + "twig/twig": "1.16.*@dev", + "erusev/parsedown": "dev-master", + "symfony/yaml": "2.5.*@dev", + "symfony/console": "2.5.*@dev", + "doctrine/cache": "1.4.*@dev", + "tracy/tracy": "dev-master", + "gregwar/image": "dev-master", + "ircmaxell/password-compat": "1.0.*" + } +} diff --git a/index.php b/index.php new file mode 100644 index 000000000..fda4afaba --- /dev/null +++ b/index.php @@ -0,0 +1,42 @@ +store('Grav', new Grav); +$registry->store('Uri', new Uri); +$registry->store('Config', Config::instance(CACHE_DIR . 'config.php')); +$registry->store('Cache', new Cache); +$registry->store('Twig', new Twig); +$registry->store('Pages', new Page\Pages); +$registry->store('Taxonomy', new Taxonomy); + +/** @var Grav $grav */ +$grav = $registry->retrieve('Grav'); +$grav->process(); + +ob_end_flush(); diff --git a/system/autoload.php b/system/autoload.php new file mode 100644 index 000000000..1e3911cef --- /dev/null +++ b/system/autoload.php @@ -0,0 +1,10 @@ +AVHBCt=5$(VX*jL6 zBMB0O5lSf$QM6eSO@e9~6bb74d$IF7JHNknx}NjA=e_s6?>Wyo_qoq~^Zb21R1~!p z0RVuC=jl@c0Kgv8-B6H|Rea0SJ!DBP@uX*vf`Woz%Jlx~Nnti9~w;{ym*er_pFVJv}uwH5?9SVq#)o zV4$+Ha%N_R&1Q?mVg`dTJUram+S=IIn0Zy=Eo1pI;dDqM0I;ufH}){#716Tdw@5cI zG7x?ViGm-bNI>ZjoUM&moDC-qh=1 z1eBYXnHSk7g|yHR^M{f4#p5Yp0xm4s+Uof_OCB17@~!Vg*hPl3@A)v5r#Y-RnpGH~ z7}5j-PtaO8_xmUAQGVoE_rlr^ho`F{t>723XYg8e`gNS|B`~9(MYJyYO&7Fn`Mrqz;z=|%DQ$t8 zT32#Qzi6PNejNhT_aX;lJ3%XPcCwWSIu9e&*4K6&Nf)MC3*~X+U`EojaB7{SYDCnH zqzJ-+P*&UBDmTVZ6v4$PeyVqQ9dr&(c2>&vg^SfsXH{~ylf9{YB3*n11Qg&dH^8vKD;fPb;SA9OYpxMUHoqt7b6mI8=JyZT z4tKiZugigW5(Iq6FIQ?^(wEHV$I;g_epLeTU;&*#oGPr)Rkf%vgbqnsLN4lCBhMtL zhvEygxX1{5QYDDn8_H6dPLFtxnJZ?CXo;6`qrUse%fW9`Y?#!x5q~!q4})uXey4b~ zt#W~q|3rbinjeT%SDMiLk;@y!(qqu;%0xmPn!e)~=(1&!~!f>OVZof7Y>r%2bMk#N| zi5j{%x6Y7wyx|&+&CM-SKTe4+i<_cnO165Khw-{8!;xl{_bJkq$5#VOq$glb2w{$f zNjCp(uM$*PU%de#=%O9FFL+2l7$`lLhYD|3E3uEP8xQZt9Sg6xEV`D2nq? zbKdfNce9#u{^DYQ^|$tEZ?{9euzZpCsO5^B1o%^jhUsG>z94rx~9Vg|b^Nk#GSs40H?}D3GfzmLZS#+5$ zx{X7#HjpfM2q|{Yzk_t+t9kAWojh|^fEM{|Bnmj^%Doo``TT8`jiNICmC;H7)*H;& z=x4iaGSk+?nKrKqN*EF^m8P1Dgu@0#8C7T0&A*W+3xe&|nLRVXRA+7bKcDzkTuKdJ1&S#4bCI7b zrQb?Qe}GfH(WDbJ&H~G@Xp|i zChWrlEWKrg;jK)Qok-zgc4UTD`41q0bf4bls7ubzL+Q)>4qK-liI;HBGJR&nziprb zmR;^SIx|w1iiupuu3)cE%a`K9zjyLC|Cm|$kMI9~V!peu>J7Rsgq7IJMmNCI&G%IG I$;hm~02mTM-v9sr literal 0 HcmV?d00001 diff --git a/system/images/assets/thumb-gif.png b/system/images/assets/thumb-gif.png new file mode 100644 index 0000000000000000000000000000000000000000..5f5e5035102914e51f0654833397fb2afdd80fee GIT binary patch literal 2809 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 Q>Jvj?JpDXsw}l@68%O4u$N&HU literal 0 HcmV?d00001 diff --git a/system/images/assets/thumb-html.png b/system/images/assets/thumb-html.png new file mode 100644 index 0000000000000000000000000000000000000000..c8a6cfbdc4829ca5dd17b08a3b137287a6d19a86 GIT binary patch literal 1797 zcmbVNdsNa_9H(6QU|FYpm71=Hl~NunGbtlq`I?XU{HZg8!ctRGAZ$LGHZxsjtCZ%_ z0;{9)L;1OwAwCnr&|*%3l9@R(1PsbD6BUNrIXheDY}Nkwo_p`-e(w3+`~7~t-&+_G z(Rl@SaETjJ;I6$*nbK+Kzgo(>7B%JI<+dUy}Na03Ga$#C!!-7+TR^Kfqpg)%)o zEf$Nbs;cO8I+x33GMN&IWO#VEy}ex^5YT9}-rioBOa?(vPfrh<&6Y}~EEY>B6n1rW z)zs9~*Vm7akN5TUjgF4;c)Y>E!IqYm#>U28_}4U@7sF$|(RdilxO3hX@Nm_Kbf6W% z8z6+?5(p%8Iu_=B2#3Mi?n_L^l5pwCcw5h~9b?T6XLUT2d7fuF7EMURrP_ukreI+% zj!uqF+f%;N6Tx7M)P7#>;Tcz+OVYw>k68Jn@d6%SGQ4+ti4rPW^?p` zt9G!uekSWQKPk@o`LoCXt^VwF&ceb)%i-(Hd=cjXTWBvP_g{e{#$cz?b%t4laF{CD zX0>j$g0xajkP*#uE*$~e5~#J^;*J8tyGU-1`bLXeex;q60v%b4fArbJP73g2Yz4}6 zEprX@=2dKkiry~Z>;;DcfQq(MZpFrB3c*%@c3V}L2EemA)Ek%X3bhcSH_?RlL#*G%KtB`#X#>by0-*BpzcT@)$Bg4d`3j8!1%lA^tT_Z*)o-ho5I`nqTTzXiBVR z#6j(Kz=0?P!@=yKDSW(W(Q)oVHYWE2U~31#O@$K537)bTIoVaAbo%Oj5tC@)NP8Fk-PTamj7#M0>_Uz5q%BFmCgJ;%(CvVe)*Mk9VV=Qy-X| zCiajU_?_oW3Y;}tuRjr?eTq7ft(y-{nvtk`L2Tvagy1y&A`TMq0_%YKWc9}7ij2#1 zkNIop7$`y8G->wMHRDqrvhMI~c+W`d(j1dwV$N}7AAVZQkW$INK@U(Hl--CoQ?b#k z67>m�nr?d`}6yQzW-yB=_H^4Li<2g=KR{N|7{dqEdp#zKo7;qo5d)qi;q^BF!Wk z&r9*mue<{DiF}lOXnj~OJ7l|dmP)P0(lE#BYO+oWq#DnsT(5$a$@XAeW`Fwloo91b zwf+%N@RT;BwvP02V%hZk?jEoNZsK6m%p71lpEzSfI(#GE^#a@?+2EQyp1qsXInPN4 zUkCYaIRH{(9atCl*<{K!T0qj10Sz<(!k>9(CZo+AY)-I&KsQjX_TJNX4eh{!M-nm@_|Ow9(fUeGbl z>wyTZ9!E}`rh86_k{(~4-RA+C9f~ES((hIen$TocM<`pXY8QDd)NxJmvDzVbBSXDFIs zr(Otz%0of>?#lQ-XWPWWn?Rr#5dVO2E-bYZ6`RXBO)otv0GIvMD-?I(M9g3YB?z>1 z><>IUsVOhZ-M@H8YhN6?*jZ@3;qOf}@NL6T5U+r288~ZgbINvgeqx&Ube%?V|bsh)u Feh1XaVs`)l literal 0 HcmV?d00001 diff --git a/system/images/assets/thumb-jpeg.png b/system/images/assets/thumb-jpeg.png new file mode 100644 index 0000000000000000000000000000000000000000..cd099a7fb96881db64cfbfeb27bb8e679a48fc17 GIT binary patch literal 3072 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 literal 0 HcmV?d00001 diff --git a/system/images/assets/thumb-m4v.png b/system/images/assets/thumb-m4v.png new file mode 100644 index 0000000000000000000000000000000000000000..d5bb49a89a22cac8a6c134638b1017d719aff2b6 GIT binary patch literal 2259 zcmbtWX;72d5{*(C41$he95ZaAqJkOO93kMqQ$Rq*sBHN_P!fnBVKYG3<)I=BQBoiT zL5K?v*+R@GYuHH)h+z>SfDjPb0uvz0DqDDgU+<4q*1R8Gefw0`?Q^=i>)yXEqb=oO z2p9wck+-%ow}n8qAvaA{Mv9=bZ(B;8OyD_dds$i8{?C_&q@nZWKkY4^KYuO|2wGZN zva_>`ii%h)*7EXlPfrh*%kAjsC@wA*3WbG*g*7!bAPDB?=QlJoynXw&w6t_=Y>dz6 zx3{;CjEt0*mrqYmH#IehM54~l&YYZ_s;VmBquZpk%B}z_r$7iqfwO7bn*8%Tq@Z$; z1rTKCegR6~@ge?1LZG^t-RX(y*AJw5N}G9RL_9X=roW%M{Y_sy z#6VkDTlbW2l1w)Q0)1<3e%3xTb9w+9@xoB){%5r1NuL9*rKKFkz{Z9F=BlF1k$nH5 z%%4M_YFF);d>mn@0`(T=<3pBGR(GW+f&Y%*VUASCZTm?s9{AUr?T6rd{~3h%Yk~MJ zQY-Jq+=*Yho3xhx{(5?2ykX_|-*i2PFedEGtCM^b^BVD$5on5HDzGqFy`Bp6Pg3u! zKo=0uiQg&yE@okgDcDUr)2;{YW|j-|XoE*x)P9uWc{l#uL>B&xO57!VsH5NgQCMU6 z`g;kSoecIZx-OMn{s{P1(Iy@ZI7|XX<48CqSi&hypHP(a?q<&4xCIBCiw*Z6bDT2e zsPB+%P#Y}V^PYw&xtN_y@c=ty)zO(1#rdJQI86by&Y|pP}{eaz%>Ji3dIS#^r zOy(f;$vh51mu&Oq0*#BuX|xTW13%oj1xo22gU{MLE3WWBhVDxvrv_XM00B)*1w0{j zu`(zL$j6MO#ivF-3=0DeGd>{Y30+Yjw854|7bMp3=G4P34v!ul1OHPyV!X-wf_rn6ztxt)#(isy>@A528Y?vN)+t_NEh|~t35lxU zAhx+qbn`TT5dZR6+9_k(@tB(l8h{?C^}Epaz#fg zLlTsjWwlXFd84emP+q_(@cPMs!HY@TsRxdfiIWeq+YY03_f!Sm@l>^v;RSk6N3*>n zfezHE+^iu?s63Kl<6qiS+J<~y%^+vqu+t@`&)$C&T#gV=-1;0+29x{xB z+2zIZoj68Tx@Q<0@xE>ft<74fSZ`ppp0vl&Q+AcYR8%&!BEtC0Ozt#WDXr4XF!bKX z$XP!VS>y;6(LGp)n%&p3AsYw_7QW96ST`l*UX_TUI!rea(HT13Qtj>> zj4;=Fhlu3udSFE|!zCKr2d7l4@SUI3Q()99y*2%}zGBmVU0c!r?ZNMvW;vSnY`qhv zz54ZWe3bhkVBJ`8{LbrnB`vJe6R{$F5$9`6NOW6xRYth@98Ptubn4n$sDJMYU82lL zd3jlm_eW{>Op~QJTVzO`gK5@x2Ii0}!*^U={}y92JhrK>Ss&cGtS!y%i3 zck~hanu9IBk3O1<5j^yIf^ZaPn@~Rzv}f%3a{O&+4ZKfI<(|KrV&s> za0zi6Fex8aD!}pE%OkYi!71fy8h&t6jrl)$r_GI(#W`F(*{TcFJu=iKi67cCYmoB#j- literal 0 HcmV?d00001 diff --git a/system/images/assets/thumb-mov.png b/system/images/assets/thumb-mov.png new file mode 100644 index 0000000000000000000000000000000000000000..72b664b9701b9d4f32dae5a37b4fba8687d01122 GIT binary patch literal 2409 zcmb_ecTm&W8V&2}BF(2FMMcO`1c?;s;39-3JyL?9NK1kcX$cS%k>UbUgdirA2ue33 zAp8Jdd-TTcHyAbE`oxBujU+I^M~#*I~R-M;^L{PslL9x z($doE>gv|k*3He$v9YlyPoC7**AEX5cXf5K*=#D6+Su4wS64@&P@0;WIy*ZT7Z*oI zM+XN7-@JL#($d0YGPztXhr@aC;zfCRIfKDS*ip*i@3J@SECL4tiFJGnfv(W|NIrQq z!s2{{b0|6j?~6r&Op&2}C^^eOED9fr#o*-3oORzl0y6kI@ozdaEXp?`Ff>HYB`_ET z(o@$|*VGQq-o*le1TR>do4Q2b;TCeE$ok?I&SMvpeB&wp^PT#!J7aegEairh76ctWegDL)_b`$noWqet7bBXCk0&N=UgekZ)`;rB>S$~|u9e^zgj!WY?}c(m zuCBTHVjJ=-HR?#;{4y%`%bSA!i1yHlcv_WBfZ;s;5|jj&Xad`+qL5fFwL<=oBxns)4O`U3p^ zKk4d5*XvU-k$ zm*A=NYNZK1WP^LVod!q{yee-`gWw?#az{}^+7F;<;% z25}G_xS@m@p^rn66gyQn=KzxAsp`{|WZ7P!TJ)TE9k1vFkVv!J;=8R(rfG0!|8jc# z@wV#5j8+3JY%uxHZ)fpA2;9x!Js6J}yayyc#{Iok0JZy?6V@4kwvhoaVs7@zWCr9W zZ_mSDZ5O$XLN@Wk zQQZXLO|X>Omk%dEw|AQ$eh72~<502y$G!MJmGKQQ^@dl2x+XGk+b_O_>EHdCjb8z_ zqf^0D!)I!*(o5r>u^=|ljeEFAye;AxLd+KaFN_XS=;4IqB);3y=r}w^Jhe3Nz z0FEKpfXiQ2Af8^iT_LiuxtIr)tKG=1@}RS_2l72(kB9Z`PAAWf}%*Z z3xS9}MTs$22}F|0V-y7 zdLStcVr0!I8VUZ~;BnOGE?LIDo@|Em3f~LQB++JeLR7;oNs-L3R-RtawCBREB3GyT zimEcp_ff=PTXJn#@S_cUBv$`oxaDnRWKi>YE-KDBrXwm$r%3?)^9Jssu4q0~2eD+b zWd=8@rCT-Yb19E}aRx_DB?SSr`2($+U=HiZMbUoB%E+0E>M4ndd9;1fR*A?OZZ`Oo zHOcgyme5T~1A-1t^R=j&?6OyM+H64Iq=B}X)+M3HkTR~#veLl^BQvmqB8?-u*DIK` z#^L^8p@bgso31v$>A@xeo8m8f@M68%=M88cdz4TCW@aR5ncNC))I*f0mr?9o6x~4k zVM+j-ZE68GYFLIoaPuG;+`Q>PLCc@4yk;ing3J%U?MZGv3p``ue5IcOV;bTu2eU%)3rDQ9 z;v(B-agE|ZJ@g6R{h?*$3QQ$?jfI-imNtIBBj;n#zaUT=3ceo7}q9;>-r zTiMb*YC8u-wqPha{q5Kq5qg#~r7R2#Or{)ONJ205n4C!x&enPF)4i$h*D>JxT2Z3| zibPOTSyG0X6U|Mn%A&OLZomwM4jKu))WArPEIC5@m8wN_SFj$rtZ?DZ7zQrh=+rEQ zfG??iw8xS=OlD_&H32&2u-Bf(y3b*VWDSAa<Y$BEfZ0h^p_m)$g_1f8*MBJ{0VFKXv?YJpU&G NvbJzAuYvj`{ss84YMB54 literal 0 HcmV?d00001 diff --git a/system/images/assets/thumb-mp4.png b/system/images/assets/thumb-mp4.png new file mode 100644 index 0000000000000000000000000000000000000000..ba66476b68436f6126b555d44bcde5c0ef20add4 GIT binary patch literal 1978 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 N%G&WnvE?6E{|2^zGqeBz literal 0 HcmV?d00001 diff --git a/system/images/assets/thumb-txt.png b/system/images/assets/thumb-txt.png new file mode 100644 index 0000000000000000000000000000000000000000..bbc9b71b64937484ba471cfc2f16a18623dec389 GIT binary patch literal 1853 zcmb7FdoAvGAQ+)ckGWo8(eoy9ID)-^eW(A6bEqfx zUZH^zk=QUi#6BPthu3u^gyADY!-(O!$Gt4Zn;xZ0^HkRJj)&o~5rohXU2j4#9%2bI zg_)WM-<07(AhKr82z&3Sd}04^*nOn>Wo&!1dT+X^D|revy=KkJByN`U3>3w+`%Q7sn-#%W`8X92e~_liu3 zS*$@fkf4ANXNhg14mJZYFnNe%n~;2MpBnLq0mqbO5@uBlB%Rr##6s2}$wCo|O;?Tj zQ5Fa1Q>+VVBaSL5VyF_=c>0j3qEX8>K_=<-jc1e#baLQh3WuPMs!t{PwmW6)-(kel z5EyyzQaylNTY0xPvI>Usf|-0Et;pU1%iy;fUXoS4OzlinJzPmS2Og~jicT57GEy5? z-9CSHOEhaU1DCjgaBzFWILuC4P{m|+ujGhAX?DxO<1h0n?H$D5 z(8vGC!3+Ix@WKTsOr}&$(_4kB2j%JoxqATp`9S~r)4|7DqZQXThT-=(F}-)5QC`FX z_;s%+@I?tIds$fO+K&BJbL(i2?Hmx-mSM2dsOwqX$32!sNb=b$J>2LYtfZ(VL`3&N zpjkMD_`#(eHnK{?K=R_nW{w5=Y}B`QI z?lTi~cHU+;JVHmt=?6{%?L_iJ(nK$K%fov2PbOv1Ygr-ZvvQO_)stpY{e8;4mQU5Y z`!h9^QA_Xpc(^*D(V0qcEM3m1pu#mWaJq05n&Hz+LXjG0%ohq~F~Zv4|L*;Tj>tFT zzSM|Ic)Z18p|BfMCV@fcBYZ9FZ1NAHTFec#>Jz0+RU_AVRt^RsiK%Tat$*RDC+^o# z35y3$le2Y7O9l7JU0SPf)Ob;uP$4J9i}mw9p38&w96Ht?>6n-`*a+|2#TDw0AV4$} z#bdikQ^i4Y3jV?J0$V3D>3^rv7YEjgOn~1XkwZAg RBqqS!oGn2?zut z?d0g-3W4m=-xhIEA>v$?cviTGM%y`|#KpyjCy?X9E!O1%3Q<~Gy0*49FfdS7R#sC} zL!;4#hld#q#>B(~o6YX%=%7$2Boe8+ySuNiZ+?EBN~N~8w$9GZzIyeF!{Jm_Rn^ti zEiNwh_xDdvPcxa!k&%(+=H|x6#xWU0h47XGR~#|X5Qq$YTlO&Vk1q>Bl^6sNg6{A-0$C{(9)xr`AlLI)=F)jh6;DYYLuC zpJuQ~`dL9y3dFGvfsX%;+xs*WZUgGOQBILp`0yUzjBb68TTH2>t^h00uE^Er{l`m> zC|iFx@QW)X)J`bAKN+`{z+O5PlMjHg=xS(B!jyR_FjzB0RIHLui|xj95*tK3{MEVk z2S{=4@~}(l+>$`DLL{@x92BtxvrTcwQ*a;7V@@jIf-7+V!y5QNyZ-h94j`@oytF3g z%ud}H?TzkZKRwn4+t2&g+)PdHjMqt{N-66~kaW`I%}_uc@X$m5wVuM9yySBFzIcX6 ztN#rjip?%y86u`PuYehUpGS~_cNg&p*VA^a&18|QF$%Q{^6x^iloM3cR}!?X$#e7n zBUDe*OZEuuP&=Xo7wqo9>))- z^BYE>c~3^#h~Z)|tFg!N>uv5C4Po=gE9Yd>-F?gBk+A9V->O-!p(GWb#+#hP7o+i` z&s%7!U=i5)2r%-wuE(YV~8O!kCYfDDp z44jASy=$I?JiMAH|)~U-|c}Em{qc^!4?zV6Ug`BTwZzu*EYA zwDGE1dUdfZddf!+j?O2H*U#yKp@euGjMuz*X9LJPGC>8(sL`61Xp13f?;@`>s_ON? zwy77`Y_*2kF6G2+T^Syl7JCk>PbZ*iyRaiFXl(M4EX>k4+@wvsj&V*4n^XA_Lph<} zsYiL^{+v~KSIt?p)oW-$8C=@C6=XoDkWMx%FNss-u)G{ANh3Xafy!JLyU{hBHS!w` zl~X>O;(9AeNNdp4MZikETjFPhX!&F?xP5kQ|KFlux1|j4Wr|tsp{>1oU!rpl>RmKl z*IvegbF+T#P%tLuD7@Y`h3cKXcp%tG4O}u}e`)9^L%oX^zKE?JeVLRCo=_R8Yx%u5 zM<1Bmzg85GgSfg1Ts|~vxt#|dTK<=rf@d-zD_6}1*>aM=20~h@U`(DOsd>zjFZVTz z@^7LY9NG$+7db zql2<-I51L=;;bYvxk_>U_CWm>=riv;n|vz-o5WFmqamM*get('system.cache.prefix'); + /** @var Uri $uri */ + $uri = Registry::get('Uri'); + + $this->enabled = (bool) $config->get('system.cache.enabled'); + + // Cache key allows us to invalidate all cache on configuration changes. + $this->key = substr(md5(($prefix ? $prefix : 'g') . $uri->rootUrl(true) . $config->key . GRAV_VERSION), 2, 8); + + switch ($this->getCacheDriverName($config->get('system.cache.driver'))) { + case 'apc': + $driver = new \Doctrine\Common\Cache\ApcCache(); + break; + + case 'wincache': + $driver = new \Doctrine\Common\Cache\WinCacheCache(); + break; + + case 'xcache': + $driver = new \Doctrine\Common\Cache\XcacheCache(); + break; + + case 'memcache': + $driver = new \Doctrine\Common\Cache\MemcacheCache(); + break; + + case 'memcached': + $driver = new \Doctrine\Common\Cache\MemcachedCache(); + break; + + default: + $driver = new \Doctrine\Common\Cache\FilesystemCache(CACHE_DIR); + break; + } + $this->driver = $driver; + } + + /** + * 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. + * + * @param string $setting + * @return string The name of the best cache driver to use + */ + protected function getCacheDriverName($setting = null) + { + + if (!$setting || $setting == 'auto') { + if (extension_loaded('apc') && ini_get('apc.enabled')) { + return 'apc'; + } elseif (extension_loaded('wincache')) { + return 'wincache'; + } elseif (extension_loaded('xcache') && ini_get('xcache.size') && ini_get('xcache.cacher')) { + return 'xcache'; + } else { + return 'file'; + } + } else { + return $setting; + } + } + + /** + * 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) { + $id = $this->key . $id; + 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) { + $id = $this->key . $id; + $this->driver->save($id, $data, $lifetime); + } + } + + /** + * Getter method to get the cache key + */ + public function getKey() + { + return $this->key; + } +} diff --git a/system/src/Grav/Common/Config.php b/system/src/Grav/Common/Config.php new file mode 100644 index 000000000..0228c5db9 --- /dev/null +++ b/system/src/Grav/Common/Config.php @@ -0,0 +1,256 @@ +filename = realpath(dirname($filename)) . '/' . basename($filename); + + $this->reload(false); + } + + /** + * Force reload of the configuration from the disk. + * + * @param bool $force + * @return $this + */ + public function reload($force = true) + { + // Build file map. + $files = $this->build(); + $key = md5(serialize($files) . GRAV_VERSION); + + if ($force || $key != $this->key) { + // First take non-blocking lock to the file. + File\Config::instance($this->filename)->lock(false); + + // Reset configuration. + $this->items = array(); + $this->files = array(); + $this->init($files); + $this->key = $key; + } + + return $this; + } + + /** + * Save configuration into file. + * + * Note: Only saves the file if updated flag is set! + * + * @return $this + * @throws \RuntimeException + */ + public function save() + { + // If configuration was updated, store it as cached version. + try { + $file = File\Config::instance($this->filename); + + // Only save configuration file if it wasn't locked. Also invalidate opcache after saving. + // This prevents us from saving the file multiple times in a row and gives faster recovery. + if ($file->locked() !== false) { + $file->save($this); + $file->unlock(); + } + $this->updated = false; + } catch (\Exception $e) { + throw new \RuntimeException('Writing to cache folder failed (configuration).', 500, $e); + } + + return $this; + } + + /** + * Gets configuration instance. + * + * @param string $filename + * @return \Grav\Common\Config + */ + public static function instance($filename) + { + // Load cached version if available.. + if (file_exists($filename)) { + clearstatcache(true, $filename); + require_once $filename; + + if (class_exists('\Grav\Config')) { + $instance = new \Grav\Config($filename); + } + } + + // Or initialize new configuration object.. + if (!isset($instance)) { + $instance = new static($filename); + } + + // If configuration was updated, store it as cached version. + if ($instance->updated) { + $instance->save(); + } + + + // If not set, add manually current base url. + if (empty($instance->items['system']['base_url_absolute'])) { + $instance->items['system']['base_url_absolute'] = Registry::get('Uri')->rootUrl(true); + } + + if (empty($instance->items['system']['base_url_relative'])) { + $instance->items['system']['base_url_relative'] = Registry::get('Uri')->rootUrl(false); + } + + return $instance; + } + + /** + * Convert configuration into an array. + * + * @return array + */ + public function toArray() + { + return array('key' => $this->key, 'files' => $this->files, 'items' => $this->items); + } + + /** + * Initialize object by loading all the configuration files. + * + * @param array $files + */ + protected function init(array $files) + { + $this->updated = true; + + // Combine all configuration files into one larger lookup table (only keys matter). + $allFiles = $files['user'] + $files['plugins'] + $files['system']; + + // Then sort the files to have all parent nodes first. + // This is to make sure that child nodes override parents content. + uksort( + $allFiles, + function($a, $b) { + $diff = substr_count($a, '/') - substr_count($b, '/'); + return $diff ? $diff : strcmp($a, $b); + } + ); + + $systemBlueprints = new Blueprints(SYSTEM_DIR . 'blueprints'); + $pluginBlueprints = new Blueprints(USER_DIR); + + $items = array(); + foreach ($allFiles as $name => $dummy) { + $lookup = array( + 'system' => SYSTEM_DIR . 'config/' . $name . YAML_EXT, + 'plugins' => USER_DIR . $name . '/' . basename($name) . YAML_EXT, + 'user' => USER_DIR . 'config/' . $name . YAML_EXT, + ); + if (strpos($name, 'plugins/') === 0) { + $blueprint = $pluginBlueprints->get("{$name}/blueprints"); + } else { + $blueprint = $systemBlueprints->get($name); + } + + $data = new Data(array(), $blueprint); + foreach ($lookup as $key => $path) { + if (is_file($path)) { + $data->merge(File\Yaml::instance($path)->content()); + } + } +// $data->validate(); +// $data->filter(); + + // Find the current sub-tree location. + $current = &$items; + $parts = explode('/', $name); + foreach ($parts as $part) { + if (!isset($current[$part])) { + $current[$part] = array(); + } + $current = &$current[$part]; + } + + // Handle both updated and deleted configuration files. + $current = $data->toArray(); + } + + $this->items = $items; + $this->files = $files; + } + + /** + * Build a list of configuration files with their timestamps. Used for loading settings and caching them. + * + * @return array + * @internal + */ + protected function build() + { + // Find all plugins with default configuration options. + $plugins = array(); + $iterator = new \DirectoryIterator(PLUGINS_DIR); + + /** @var \DirectoryIterator $plugin */ + foreach ($iterator as $plugin) { + $name = $plugin->getBasename(); + $file = $plugin->getPathname() . DS . $name . YAML_EXT; + + if (!is_file($file)) { + continue; + } + + $modified = filemtime($file); + $plugins["plugins/{$name}"] = $modified; + } + + // Find all system and user configuration files. + $options = array( + 'compare' => 'Filename', + 'pattern' => '|\.yaml$|', + 'filters' => array('key' => '|\.yaml$|'), + 'key' => 'SubPathname', + 'value' => 'MTime' + ); + + $system = Folder::all(SYSTEM_DIR . 'config', $options); + $user = Folder::all(USER_DIR . 'config', $options); + + return array('system' => $system, 'plugins' => $plugins, 'user' => $user); + } +} diff --git a/system/src/Grav/Common/Data/Blueprint.php b/system/src/Grav/Common/Data/Blueprint.php new file mode 100644 index 000000000..6339f873a --- /dev/null +++ b/system/src/Grav/Common/Data/Blueprint.php @@ -0,0 +1,473 @@ +name = $name; + $this->blueprints = $data; + $this->context = $context; + } + + /** + * Get value by using dot notation for nested arrays/objects. + * + * @example $value = $data->get('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 get($name, $default = null, $separator = '.') + { + $path = explode($separator, $name); + $current = $this->blueprints; + 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; + } + + /** + * Sey value by using dot notation for nested arrays/objects. + * + * @example $value = $data->set('this.is.my.nested.variable', true); + * + * @param string $name Dot separated path to the requested value. + * @param mixed $value New value. + * @param string $separator Separator, defaults to '.' + */ + public function set($name, $value, $separator = '.') + { + $path = explode($separator, $name); + $current = &$this->blueprints; + foreach ($path as $field) { + if (is_object($current)) { + // Handle objects. + if (!isset($current->{$field})) { + $current->{$field} = array(); + } + $current = &$current->{$field}; + } else { + // Handle arrays and scalars. + if (!is_array($current)) { + $current = array($field => array()); + } elseif (!isset($current[$field])) { + $current[$field] = array(); + } + $current = &$current[$field]; + } + } + + $current = $value; + } + + /** + * Return all form fields. + * + * @return array + */ + public function fields() + { + if (!isset($this->fields)) { + $this->fields = isset($this->blueprints['form']['fields']) ? $this->blueprints['form']['fields'] : array(); + $this->getFields($this->fields); + } + + 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) { + throw new \RuntimeException(sprintf('Page validation failed: %s', $e->getMessage())); + } + } + + /** + * 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(); + return $this->extraArray($data, $this->nested, $prefix); + } + + /** + * @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->blueprints['validation']) && $this->blueprints['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->blueprints['validation']) && $this->blueprints['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); + } else { + // Undefined/extra item. + $array[$prefix.$key] = $field; + } + } + return $array; + } + + /** + * Gets all field definitions from the blueprints. + * + * @param array $fields + * @internal + */ + protected function getFields(array &$fields) + { + // Go though all the fields in current level. + foreach ($fields as $key => &$field) { + // Set name from the array key. + $field['name'] = $key; + + if (isset($field['fields'])) { + // Recursively get all the nested fields. + $this->getFields($field['fields']); + } else { + // Add rule. + $this->rules[$key] = &$field; + $this->addProperty($key); + + foreach ($field as $name => $value) { + // Support nested blueprints. + if ($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; + } + } + } + } + + // Initialize predefined validation rule. + if (isset($field['validate']['rule'])) { + $field['validate'] += $this->getRule($field['validate']['rule']); + } + } + } + } + + /** + * 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->blueprints['rules'][$rule]) && is_array($this->blueprints['rules'][$rule])) { + return $this->blueprints['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])) { + throw new \RuntimeException("Missing required field: {$field['name']}"); + } + } + } + + /** + * Convert blueprints into an array. + * + * @return array + */ + public function toArray() + { + return $this->blueprints; + } + + /** + * Convert blueprints into YAML string. + * + * @return string + */ + public function toYaml() + { + return Yaml::dump($this->blueprints); + } + + /** + * Convert blueprints into JSON string. + * + * @return string + */ + public function toJson() + { + return json_encode($this->blueprints); + } + + /** + * Extend blueprint with another blueprint. + * + * @param Blueprint $extends + * @param bool $append + */ + public function extend(Blueprint $extends, $append = false) + { + $blueprints = $append ? $this->blueprints : $extends->toArray(); + $appended = $append ? $extends->toArray() : $this->blueprints; + + $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->blueprints = $blueprints; + } +} diff --git a/system/src/Grav/Common/Data/Blueprints.php b/system/src/Grav/Common/Data/Blueprints.php new file mode 100644 index 000000000..a247741ae --- /dev/null +++ b/system/src/Grav/Common/Data/Blueprints.php @@ -0,0 +1,81 @@ +search = rtrim($search, '\\/') . '/'; + } + + /** + * Get blueprint. + * + * @param string $type Blueprint type. + * @return Blueprint + * @throws \RuntimeException + */ + public function get($type) + { + if (!isset($this->instances[$type])) { + if (is_file($this->search . $type . YAML_EXT)) { + $blueprints = (array) Yaml::parse($this->search . $type . YAML_EXT); + } else { + // throw new \RuntimeException("Blueprints for '{$type}' cannot be found! {$this->search}{$type}"); + $blueprints = array(); + } + + $blueprint = new Blueprint($type, $blueprints, $this); + + if (isset($blueprints['@extends'])) { + // Extend blueprint by other blueprints. + $extends = (array) $blueprints['@extends']; + foreach ($extends as $extendType) { + $blueprint->extend($this->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(); + + $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 000000000..27eef427f --- /dev/null +++ b/system/src/Grav/Common/Data/Data.php @@ -0,0 +1,235 @@ +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); + } + + /** + * Get value by using dot notation for nested arrays/objects. + * + * @example $value = $data->get('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 get($name, $default = null, $separator = '.') + { + $path = explode($separator, $name); + $current = $this->items; + 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; + } + + /** + * Sey value by using dot notation for nested arrays/objects. + * + * @example $value = $data->set('this.is.my.nested.variable', true); + * + * @param string $name Dot separated path to the requested value. + * @param mixed $value New value. + * @param string $separator Separator, defaults to '.' + */ + public function set($name, $value, $separator = '.') + { + $path = explode($separator, $name); + $current = &$this->items; + foreach ($path as $field) { + if (is_object($current)) { + // Handle objects. + if (!isset($current->{$field})) { + $current->{$field} = array(); + } + $current = &$current->{$field}; + } else { + // Handle arrays and scalars. + if (!is_array($current)) { + $current = array($field => array()); + } elseif (!isset($current[$field])) { + $current[$field] = array(); + } + $current = &$current[$field]; + } + } + + $current = $value; + } + + /** + * Set default value by using dot notation for nested arrays/objects. + * + * @example $data->def('this.is.my.nested.variable', 'default'); + * + * @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 def($name, $default = null, $separator = '.') + { + $this->set($name, $this->get($name, $default, $separator), $separator); + } + + /** + * Merge two sets of data together. + * + * @param array $data + */ + public function merge(array $data) + { + if ($this->blueprints) { + $this->items = $this->blueprints->mergeData($this->items, $data); + } else { + $this->items = array_merge($this->items, $data); + } + } + + /** + * Return blueprints. + * + * @return Blueprint + */ + public function blueprints() + { + return $this->blueprints; + } + + /** + * Validate by blueprints. + * + * @throws \Exception + */ + public function validate() + { + if ($this->blueprints) { + $this->blueprints->validate($this->items); + } + } + + /** + * Filter all items by using blueprints. + */ + public function filter() + { + if ($this->blueprints) { + $this->items = $this->blueprints->filter($this->items); + } + } + + /** + * Get extra items which haven't been defined in blueprints. + * + * @return array + */ + public function extra() + { + return $this->blueprints ? $this->blueprints->extra($this->items) : array(); + } + + /** + * 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 000000000..7ce6ec518 --- /dev/null +++ b/system/src/Grav/Common/Data/DataInterface.php @@ -0,0 +1,69 @@ +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 000000000..3390349f4 --- /dev/null +++ b/system/src/Grav/Common/Data/Validation.php @@ -0,0 +1,603 @@ + $params) { + $method = 'validate'.strtr($rule, '-', '_'); + if (method_exists(__CLASS__, $method)) { + $success = self::$method($value, $params); + if (!$success) { + throw new \RuntimeException('Failed'); + } + } + } + } + + /** + * 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; + } + + // 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) + { + if (!is_string($value)) { + var_dump($value); + var_dump($params); + var_dump($field); + die(); + } + 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); + } + + /** + * 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; + } + + /** + * 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) + { + // TODO: add min, max and range. + 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 typeDatetime_local($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(); + + if ($options) { + $useKey = isset($field['use']) && $field['use'] == 'keys'; + foreach ($values as $key => $value) { + $values[$key] = $useKey ? (bool) $value : $value; + } + } + + return $values; + } + + /** + * 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; + } + + // HTML5 attributes (min, max and range are handled inside the types) + + public static function validateRequired($value, $params) + { + 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/Filesystem/File/Config.php b/system/src/Grav/Common/Filesystem/File/Config.php new file mode 100644 index 000000000..d697b1f65 --- /dev/null +++ b/system/src/Grav/Common/Filesystem/File/Config.php @@ -0,0 +1,129 @@ +filename); + } elseif (function_exists('apc_invalidate')) { + // APC + @apc_invalidate($this->filename); + } + } + + /** + * Check contents and make sure it is in correct format. + * + * @param \Grav\Common\Config $var + * @return \Grav\Common\Config + * @throws \RuntimeException + */ + protected function check($var) + { + if (!($var instanceof \Grav\Common\Config)) { + throw new \RuntimeException('Provided data is not configuration'); + } + + return $var; + } + + /** + * Encode configuration object into RAW string (PHP class). + * + * @param \Grav\Common\Config $var + * @return string + * @throws \RuntimeException + */ + protected function encode($var) + { + if (!($var instanceof \Grav\Common\Config)) { + throw new \RuntimeException('Provided data is not configuration'); + } + + // Build the object variables string + $vars = array(); + $options = $var->toArray(); + + foreach ($options as $k => $v) { + if (is_int($v)) { + $vars[] = "\tpublic $" . $k . " = " . $v . ";"; + } elseif (is_bool($v)) { + $vars[] = "\tpublic $" . $k . " = " . ($v ? 'true' : 'false') . ";"; + } elseif (is_scalar($v)) { + $vars[] = "\tpublic $" . $k . " = '" . addcslashes($v, '\\\'') . "';"; + } elseif (is_array($v) || is_object($v)) { + $vars[] = "\tpublic $" . $k . " = " . $this->encodeArray((array) $v) . ";"; + } + } + $vars = implode("\n", $vars); + + return " $v) { + if (is_array($v) || is_object($v)) { + $r[] = '"' . $k . '" => ' . $this->encodeArray((array) $v, $level+1); + } elseif (is_int($v)) { + $r[] = "'" . $k . "' => " . $v; + } elseif (is_bool($v)) { + $r[] = "'" . $k . "' => " . ($v ? 'true' : 'false'); + } else { + $r[] .= "'" . $k . "' => " . "'" . addslashes($v) . "'"; + } + } + + $tabs = str_repeat("\t", $level); + return "array(\n\t{$tabs}" . implode(",\n\t{$tabs}", $r) . "\n{$tabs})"; + } + + /** + * Decode RAW string into contents. + * + * @param string $var + * @return \Grav\Common\Config + */ + protected function decode($var) + { + // TODO: improve this one later, works only for single file... + return class_exists('\Grav\Config') ? new \Grav\Config($this->filename) : new Config($this->filename); + } +} diff --git a/system/src/Grav/Common/Filesystem/File/General.php b/system/src/Grav/Common/Filesystem/File/General.php new file mode 100644 index 000000000..d9530f499 --- /dev/null +++ b/system/src/Grav/Common/Filesystem/File/General.php @@ -0,0 +1,352 @@ +init($filename); + } + return static::$instances[$filename]; + } + + /** + * Prevent constructor from being used. + * + * @internal + */ + protected function __construct() + { + } + + /** + * Prevent cloning. + * + * @internal + */ + protected function __clone() + { + //Me not like clones! Me smash clones! + } + + /** + * Set filename. + * + * @param $filename + */ + protected function init($filename) + { + $this->filename = $filename; + } + + /** + * Get/set the file location. + * + * @param string $var + * @return string + */ + public function filename($var = null) + { + if ($var !== null) { + $this->filename = $var; + } + return $this->filename; + } + + /** + * Return basename of the file. + * + * @return string + */ + public function basename() + { + return basename($this->filename, $this->extension); + } + + /** + * Check if file exits. + * + * @return bool + */ + public function exists() + { + return is_file($this->filename); + } + + /** + * Return file modification time. + * + * @return int|bool Timestamp or false if file doesn't exist. + */ + public function modified() + { + return is_file($this->filename) ? filemtime($this->filename) : false; + } + + /** + * Lock file for writing. You need to manually unlock(). + * + * @param bool $block For non-blocking lock, set the parameter to false. + * @return bool + */ + public function lock($block = true) + { + if (!$this->handle) { + $this->handle = fopen($this->filename, 'wb+'); + } + $lock = $block ? LOCK_EX : LOCK_EX | LOCK_NB; + return $this->locked = flock($this->handle, $lock); + } + + /** + * Returns true if file has been locked for writing. + * + * @return bool|null True = locked, false = failed, null = not locked. + */ + public function locked() + { + return $this->locked; + } + + /** + * Unlock file. + * + * @return bool + */ + public function unlock() + { + if (!$this->handle) { + return; + } + if ($this->locked) { + flock($this->handle, LOCK_UN); + $this->locked = null; + } + fclose($this->handle); + } + + /** + * Check if file can be written. + * + * @return bool + */ + public function writable() + { + return is_writable($this->filename) || $this->writableDir(dirname($this->filename)); + } + + /** + * (Re)Load a file and return RAW file contents. + * + * @return string + */ + public function load() + { + $this->raw = $this->exists() ? (string) file_get_contents($this->filename) : ''; + $this->content = null; + + return $this->raw; + } + + /** + * Get/set raw file contents. + * + * @param string $var + * @return string + */ + public function raw($var = null) + { + if ($var !== null) { + $this->raw = (string) $var; + $this->content = null; + } + + if (!is_string($this->raw)) { + $this->raw = $this->load(); + } + + return $this->raw; + } + + /** + * Get/set parsed file contents. + * + * @param mixed $var + * @return string + */ + public function content($var = null) + { + if ($var !== null) { + $this->content = $this->check($var); + + // Update RAW, too. + $this->raw = $this->encode($this->content); + + } elseif ($this->content === null) { + // Decode RAW file. + $this->content = $this->decode($this->raw()); + } + + return $this->content; + } + + /** + * Save file. + * + * @param mixed $data Optional data to be saved, usually array. + * @throws \RuntimeException + */ + public function save($data = null) + { + if ($data !== null) { + $this->content($data); + } + + if (!$this->mkdir(dirname($this->filename))) { + throw new \RuntimeException('Creating directory failed for ' . $this->filename); + } + if (!$this->locked) { + // Obtain blocking lock or fail. + if (!$this->lock()) { + throw new \RuntimeException('Obtaining write lock failed on file: ' . $this->filename); + } + $lock = true; + } + + if (@fwrite($this->handle, $this->raw()) === false) { + $this->unlock(); + throw new \RuntimeException('Saving file failed: ' . $this->filename); + } + + if (isset($lock)) { + $this->unlock(); + } + + // Touch the directory as well, thus marking it modified. + @touch(dirname($this->filename)); + } + + /** + * Delete file from filesystem. + * + * @return bool + */ + public function delete() + { + return unlink($this->filename); + } + + /** + * Check contents and make sure it is in correct format. + * + * Override in derived class. + * + * @param string $var + * @return string + */ + protected function check($var) + { + return (string) $var; + } + + /** + * Encode contents into RAW string. + * + * Override in derived class. + * + * @param string $var + * @return string + */ + protected function encode($var) + { + return (string) $var; + } + + /** + * Decode RAW string into contents. + * + * Override in derived class. + * + * @param string $var + * @return string mixed + */ + protected function decode($var) + { + return (string) $var; + } + + /** + * @param string $dir + * @return bool + * @internal + */ + protected function mkdir($dir) + { + return is_dir($dir) || mkdir($dir, 0777, true); + } + + /** + * @param string $dir + * @return bool + * @internal + */ + protected function writableDir($dir) + { + if ($dir && !file_exists($dir)) { + return $this->writableDir(dirname($dir)); + } + + return $dir && is_dir($dir) && is_writable($dir); + } +} diff --git a/system/src/Grav/Common/Filesystem/File/Json.php b/system/src/Grav/Common/Filesystem/File/Json.php new file mode 100644 index 000000000..b7ea18e5d --- /dev/null +++ b/system/src/Grav/Common/Filesystem/File/Json.php @@ -0,0 +1,54 @@ +extension = '.log'; + } + + /** + * Check contents and make sure it is in correct format. + * + * @param array $var + * @return array + */ + protected function check($var) + { + return (array) $var; + } + + /** + * Encode contents into RAW string (unsupported). + * + * @param string $var + * @throws \Exception + */ + protected function encode($var) + { + throw new \Exception('Saving log file is forbidden.'); + } + + /** + * Decode RAW string into contents. + * + * @param string $var + * @return array mixed + */ + protected function decode($var) + { + $lines = (array) preg_split('#(\r\n|\n|\r)#', $var); + + $results = array(); + foreach ($lines as $line) { + preg_match('#^\[(.*)\] (.*) @ (.*) @@ (.*)$#', $line, $matches); + if ($matches) { + $results[] = ['date' => $matches[1], 'message' => $matches[2], 'url' => $matches[3], 'file' => $matches[4]]; + } + } + + return $results; + } +} diff --git a/system/src/Grav/Common/Filesystem/File/Markdown.php b/system/src/Grav/Common/Filesystem/File/Markdown.php new file mode 100644 index 000000000..8fe615dae --- /dev/null +++ b/system/src/Grav/Common/Filesystem/File/Markdown.php @@ -0,0 +1,120 @@ +content(); + + if ($var !== null) { + $content['header'] = $var; + $this->content($content); + } + + return $content['header']; + } + + /** + * Get/set markdown content. + * + * @param string $var + * + * @return string + */ + public function markdown($var = null) + { + $content = $this->content(); + + if ($var !== null) { + $content['markdown'] = (string) $var; + $this->content($content); + } + + return $content['markdown']; + } + + /** + * Check contents and make sure it is in correct format. + * + * @param array $var + * @return array + */ + protected function check($var) + { + $var = (array) $var; + if (!isset($var['header']) || !is_array($var['header'])) { + $var['header'] = array(); + } + if (!isset($var['markdown']) || !is_string($var['markdown'])) { + $var['markdown'] = ''; + } + + return $var; + } + + /** + * Encode contents into RAW string. + * + * @param string $var + * @return string + */ + protected function encode($var) + { + // Create Markdown file with YAML header. + $o = (!empty($var['header']) ? "---\n" . trim(YamlParser::dump($var['header'])) . "\n---\n\n" : '') . $var['markdown']; + + // Normalize line endings to Unix style. + $o = preg_replace("/(\r\n|\r)/", "\n", $o); + + return $o; + } + + /** + * Decode RAW string into contents. + * + * @param string $var + * @return array mixed + */ + protected function decode($var) + { + $content = array(); + + // Normalize line endings to Unix style. + $var = preg_replace("/(\r\n|\r)/", "\n", $var); + + // Parse header. + preg_match("/---\n(.+?)\n---(\n\n|$)/uism", $this->raw(), $m); + $content['header'] = isset($m[1]) ? YamlParser::parse(preg_replace("/\n\t/", "\n ", $m[1])) : array(); + + // Strip header to get content. + $content['markdown'] = trim(preg_replace("/---\n(.+?)\n---(\n\n|$)/uism", '', $var)); + + return $content; + } +} diff --git a/system/src/Grav/Common/Filesystem/File/Yaml.php b/system/src/Grav/Common/Filesystem/File/Yaml.php new file mode 100644 index 000000000..e93e0e03e --- /dev/null +++ b/system/src/Grav/Common/Filesystem/File/Yaml.php @@ -0,0 +1,61 @@ +extension = YAML_EXT; + } + + /** + * Check contents and make sure it is in correct format. + * + * @param array $var + * @return array + */ + protected function check($var) + { + return (array) $var; + } + + /** + * Encode contents into RAW string. + * + * @param string $var + * @return string + */ + protected function encode($var) + { + return (string) YamlParser::dump($var); + } + + /** + * Decode RAW string into contents. + * + * @param string $var + * @return array mixed + */ + protected function decode($var) + { + return (array) YamlParser::parse($var); + } +} diff --git a/system/src/Grav/Common/Filesystem/FileInterface.php b/system/src/Grav/Common/Filesystem/FileInterface.php new file mode 100644 index 000000000..a090e550c --- /dev/null +++ b/system/src/Grav/Common/Filesystem/FileInterface.php @@ -0,0 +1,100 @@ +getMTime(); + if ($dir_modified > $last_modified) { + $last_modified = $dir_modified; + } + } + return $last_modified; + } + + /** + * 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 = array()) + { + $path = realpath($path); + + if ($path === false) { + throw new \RuntimeException("Path to {$path} doesn't exist."); + } + + $compare = $params['compare'] ? 'get' . $params['compare'] : null; + $pattern = $params['pattern'] ? $params['pattern'] : null; + $filters = $params['filters'] ? $params['filters'] : null; + $key = $params['key'] ? 'get' . $params['key'] : null; + $value = $params['value'] ? 'get' . $params['value'] : 'SubPathname'; + + $directory = new \RecursiveDirectoryIterator($path, + \RecursiveDirectoryIterator::SKIP_DOTS + \FilesystemIterator::UNIX_PATHS + \FilesystemIterator::CURRENT_AS_SELF); + $iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST); + + $results = array(); + + /** @var \RecursiveDirectoryIterator $file */ + foreach ($iterator as $file) { + if ($compare && $pattern && !preg_match($pattern, $file->{$compare}())) { + continue; + } + $fileKey = $key ? $file->{$key}() : null; + $filePath = $file->{$value}(); + if ($filters) { + if (isset($filters['key'])) { + $fileKey = preg_replace($filters['key'], '', $fileKey); + } + if (isset($filters['value'])) { + $filePath = preg_replace($filters['value'], '', $filePath); + } + } + + $results[$fileKey] = $filePath; + } + return $results; + } + + /** + * Recursively copy directory in filesystem. + * + * @param string $source + * @param string $target + * @throws \RuntimeException + */ + public static function copy($source, $target) + { + $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::mkdir($target); + + $success = true; + + // Go through all sub-directories and copy everything. + $files = self::all($source); + foreach ($files as $file) { + $src = $source .'/'. $file; + $dst = $target .'/'. $file; + + if (is_dir($src)) { + // Create current directory. + $success &= @mkdir($dst); + } 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.'); + } + + // Make sure that path to the target exists before moving. + self::mkdir(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 + * @throws \RuntimeException + */ + public static function delete($target) + { + if (!is_dir($target)) { + throw new \RuntimeException('Cannot delete non-existing folder.'); + } + + $success = self::doDelete($target); + + if (!$success) { + $error = error_get_last(); + throw new \RuntimeException($error['message']); + } + + // Make sure that the change will be detected when caching. + @touch(dirname($target)); + } + + /** + * @param string $folder + * @return bool + * @internal + */ + protected static function doDelete($folder) + { + // Special case for symbolic links. + if (is_link($folder)) { + return @unlink($folder); + } + + // Go through all items in filesystem and recursively remove everything. + $files = array_diff(scandir($folder), array('.', '..')); + foreach ($files as $file) { + $path = "{$folder}/{$file}"; + (is_dir($path)) ? self::doDelete($path) : @unlink($path); + } + + return @rmdir($folder); + } + + /** + * @param string $folder + * @throws \RuntimeException + * @internal + */ + protected static function mkdir($folder) + { + if (is_dir($folder)) { + return; + } + + $success = @mkdir($folder, 0777, true); + + if (!$success) { + $error = error_get_last(); + throw new \RuntimeException($error['message']); + } + } +} diff --git a/system/src/Grav/Common/Getters.php b/system/src/Grav/Common/Getters.php new file mode 100644 index 000000000..699f9153b --- /dev/null +++ b/system/src/Grav/Common/Getters.php @@ -0,0 +1,150 @@ +offsetSet($offset, $value); + } + + /** + * Magic getter method + * + * @param mixed $offset Asset name value + * @return mixed Asset value + */ + public function __get($offset) + { + return $this->offsetGet($offset); + } + + /** + * Magic method to determine if the attribute is set + * + * @param mixed $offset Asset 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 = array(); + 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 000000000..49c42f070 --- /dev/null +++ b/system/src/Grav/Common/Grav.php @@ -0,0 +1,214 @@ +uri = Registry::get('Uri'); + + // Get the Configuration settings and caching + $this->config = Registry::get('Config'); + + Debugger::$logDirectory = $this->config->get('system.debugger.log.enabled') ? LOG_DIR : null; + Debugger::$maxDepth = $this->config->get('system.debugger.max_depth'); + + // Switch debugger into development mode if configured + if ($this->config->get('system.debugger.enabled')) { + if (function_exists('ini_set')) { + ini_set('display_errors', true); + } + Debugger::$productionMode = Debugger::DEVELOPMENT; + $this->fireEvent('onAfterInitDebug'); + } + + // Get the Caching setup + $this->cache = Registry::get('Cache'); + $this->cache->init(); + + // Get Plugins + $plugins = new Plugins(); + $this->plugins = $plugins->load(); + $this->fireEvent('onAfterInitPlugins'); + + // Get current theme and hook it into plugins. + $themes = new Themes(); + $this->plugins['Theme'] = $themes->load(); + + // Get twig object + $this->twig = Registry::get('Twig'); + $this->twig->init(); + + // Get all the Pages that Grav knows about + $this->pages = Registry::get('Pages'); + $this->pages->init(); + $this->fireEvent('onAfterGetPages'); + + // Get the taxonomy and set it on the grav object + $this->taxonomy = Registry::get('Taxonomy'); + + // Get current page + $this->page = $this->pages->dispatch($this->uri->route()); + $this->fireEvent('onAfterGetPage'); + + // If there's no page, throw exception + if (!$this->page) { + throw new \RuntimeException('Page Not Found', 404); + } + + // Process whole page as required + $this->output = $this->twig->processSite($this->uri->extension()); + $this->fireEvent('onAfterGetOutput'); + + // Set the header type + $this->header(); + + echo $this->output; + } + + /** + * Redirect browser to another location. + * + * @param string $route Internal route. + * @param int $code Redirection code (30x) + */ + public function redirect($route, $code = 303) + { + header("Location: " . rtrim($this->uri->rootUrl(), '/') .'/'. trim($route, '/'), true, $code); + exit(); + } + + /** + * 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() + { + header('Content-type: ' . $this->mime($this->uri->extension())); + } + + /** + * Log a message. + * + * @param string $message + */ + protected static function log($message) + { + if (Debugger::$logDirectory) { + Debugger::log(sprintf($message, Debugger::timer() * 1000)); + } + } + + /** + * Processes any hooks and runs them. + */ + public function fireEvent() + { + $args = func_get_args(); + $hook_id = array_shift($args); + $no_timing_hooks = array('onAfterPageProcessed','onAfterFolderProcessed', 'onAfterCollectionProcessed'); + + if (!empty($this->plugins)) { + foreach ($this->plugins as $plugin) { + if (is_callable(array($plugin, $hook_id))) { + call_user_func_array(array($plugin, $hook_id), $args); + } + } + } + + if ($this->config->get('system.debugger.log.timing') && !in_array($hook_id, $no_timing_hooks)) { + static::log($hook_id.': %f ms'); + } + } +} diff --git a/system/src/Grav/Common/Inflector.php b/system/src/Grav/Common/Inflector.php new file mode 100644 index 000000000..eebbdf03c --- /dev/null +++ b/system/src/Grav/Common/Inflector.php @@ -0,0 +1,370 @@ + '\1zes', + '/^(ox)$/i' => '\1en', + '/([m|l])ouse$/i' => '\1ice', + '/(matr|vert|ind)ix|ex$/i' => '\1ices', + '/(x|ch|ss|sh)$/i' => '\1es', + '/([^aeiouy]|qu)ies$/i' => '\1y', + '/([^aeiouy]|qu)y$/i' => '\1ies', + '/(hive)$/i' => '\1s', + '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves', + '/sis$/i' => 'ses', + '/([ti])um$/i' => '\1a', + '/(buffal|tomat)o$/i' => '\1oes', + '/(bu)s$/i' => '\1ses', + '/(alias|status)/i'=> '\1es', + '/(octop|vir)us$/i'=> '\1i', + '/(ax|test)is$/i'=> '\1es', + '/s$/i'=> 's', + '/$/'=> 's'); + + $uncountable = array('equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep'); + + $irregular = array( + 'person' => 'people', + 'man' => 'men', + 'child' => 'children', + 'sex' => 'sexes', + 'move' => 'moves'); + + $lowercased_word = strtolower($word); + + foreach ($uncountable as $_uncountable) { + if (substr($lowercased_word, (-1*strlen($_uncountable))) == $_uncountable) { + return $word; + } + } + + foreach ($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 ($plural as $rule => $replacement) { + if (preg_match($rule, $word)) { + return preg_replace($rule, $replacement, $word); + } + } + return false; + + } + + /** + * Singularizes English nouns. + * + * @access static public + * @static + * @param string $word English noun to singularize + * @return string Singular noun. + */ + public static function singularize($word, $count = 1) + { + if ($count != 1) { + return $word; + } + + $singular = array ( + '/(quiz)zes$/i' => '\1', + '/(matr)ices$/i' => '\1ix', + '/(vert|ind)ices$/i' => '\1ex', + '/^(ox)en/i' => '\1', + '/(alias|status)es$/i' => '\1', + '/([octop|vir])i$/i' => '\1us', + '/(cris|ax|test)es$/i' => '\1is', + '/(shoe)s$/i' => '\1', + '/(o)es$/i' => '\1', + '/(bus)es$/i' => '\1', + '/([m|l])ice$/i' => '\1ouse', + '/(x|ch|ss|sh)es$/i' => '\1', + '/(m)ovies$/i' => '\1ovie', + '/(s)eries$/i' => '\1eries', + '/([^aeiouy]|qu)ies$/i' => '\1y', + '/([lr])ves$/i' => '\1f', + '/(tive)s$/i' => '\1', + '/(hive)s$/i' => '\1', + '/([^f])ves$/i' => '\1fe', + '/(^analy)ses$/i' => '\1sis', + '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis', + '/([ti])a$/i' => '\1um', + '/(n)ews$/i' => '\1ews', + '/s$/i' => '', + ); + + $uncountable = array('equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep'); + + $irregular = array( + 'person' => 'people', + 'man' => 'men', + 'child' => 'children', + 'sex' => 'sexes', + 'move' => 'moves'); + + $lowercased_word = strtolower($word); + foreach ($uncountable as $_uncountable) { + if (substr($lowercased_word, (-1*strlen($_uncountable))) == $_uncountable) { + return $word; + } + } + + foreach ($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 ($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 static 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. + * + * @access static public + * @static + * @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 static function titleize($word, $uppercase = '') + { + $uppercase = $uppercase == 'first' ? 'ucfirst' : 'ucwords'; + return $uppercase(static::humanize(static::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" + * + * @access static public + * @static + * @see variablize + * @param string $word Word to convert to camel case + * @return string UpperCamelCasedWord + */ + public static 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. + * + * @access static public + * @static + * @param string $word Word to underscore + * @return string Underscored word + */ + public static 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. + * + * @access static public + * @static + * @param string $word Word to hyphenate + * @return string hyphenized word + */ + public static 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. + * + * @access static public + * @static + * @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 static 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" + * + * @access static public + * @static + * @see camelize + * @param string $word Word to lowerCamelCase + * @return string Returns a lowerCamelCasedWord + */ + public static function variablize($word) + { + $word = static::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" + * + * @access static public + * @static + * @see classify + * @param string $class_name Class name for getting related table_name. + * @return string plural_table_name + */ + public static function tableize($class_name) + { + return static::pluralize(static::underscore($class_name)); + } + + /** + * Converts a table name to its class name according to rails + * naming conventions. + * + * Converts "people" to "Person" + * + * @access static public + * @static + * @see tableize + * @param string $table_name Table name for getting related ClassName. + * @return string SingularClassName + */ + public static function classify($table_name) + { + return static::camelize(static::singularize($table_name)); + } + + /** + * Converts number to its ordinal English form. + * + * This method converts 13 to 13th, 2 to 2nd ... + * + * @access static public + * @static + * @param integer $number Number to get its ordinal value + * @return string Ordinal representation of given string. + */ + public static function ordinalize($number) + { + if (in_array(($number % 100), range(11, 13))) { + return $number.'th'; + } else { + switch (($number % 10)) { + case 1: + return $number.'st'; + break; + case 2: + return $number.'nd'; + break; + case 3: + return $number.'rd'; + break; + default: + return $number.'th'; + break; + } + } + } + + public static function monthize($days) + { + $now = new JDate(); + $end = new JDate(); + + $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 000000000..dffa5ab78 --- /dev/null +++ b/system/src/Grav/Common/Iterator.php @@ -0,0 +1,394 @@ +items = $items; + } + + /** + * Convert function calls for the existing keys into their values. + * + * @param string $key + * @param mixed $args + * @return mixed + */ + public function __call($key, $args) + { + return (isset($this->items[$key])) ? $this->items[$key] : null; + } + + /** + * Array getter shorthand to get items. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return (isset($this->items[$key])) ? $this->items[$key] : null; + } + + /** + * Array setter shorthand to set the value. + * + * @param string $key + * @param mixed $value + */ + public function __set($key, $value) + { + $this->items[$key] = $value; + } + + /** + * Array isset shorthand to set the value. + * + * @param string $key + * @return bool + */ + public function __isset($key) + { + return isset($this->items[$key]); + } + + /** + * Array unset shorthand to remove the key. + * + * @param string $key + */ + public function __unset($key) + { + $this->offsetUnset($key); + } + + /** + * 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 + * @todo Add support to nested sets. + */ + 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_values($this->items); + return (isset($items[$key])) ? $this->offsetGet($items[$key]) : false; + } + + /** + * @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 = array(); + 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; + } + + // Implements export functions to array, YAML and JSON. + + /** + * Return items as an array. + * + * @return array Array presentation of the iterator. + */ + public function toArray() + { + return $this->items; + } + + /** + * Return YAML encoded string of items. + * + * @return string YAML presentation of the iterator. + */ + public function toYaml() + { + return Yaml::dump($this->items); + } + + /** + * Return JSON encoded string of items. + * + * @return string JSON presentation of the iterator. + */ + public function toJson() + { + return json_encode($this->items); + } + + // Implements Iterator. + + /** + * Returns the current element. + * + * @return mixed Can return any type. + */ + public function current() + { + return current($this->items); + } + + /** + * Returns the key of the current element. + * + * @return mixed Returns scalar on success, or NULL on failure. + */ + public function key() + { + return key($this->items); + } + + /** + * Moves the current position to the next element. + * + * @return void + */ + public function next() + { + if ($this->unset) { + // If current item was unset, position is already in the next element (do nothing). + $this->unset = false; + } else { + next($this->items); + } + } + + /** + * Rewinds back to the first element of the Iterator. + * + * @return void + */ + public function rewind() + { + $this->unset = false; + reset($this->items); + } + + /** + * This method is called after Iterator::rewind() and Iterator::next() to check if the current position is valid. + * + * @return bool Returns TRUE on success or FALSE on failure. + */ + public function valid() + { + return key($this->items) !== null; + } + + // Implements ArrayAccess + + /** + * Whether or not an offset exists. + * + * @param mixed $offset An offset to check for. + * @return bool Returns TRUE on success or FALSE on failure. + */ + public function offsetExists($offset) + { + return isset($this->items[$offset]); + } + + /** + * 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 isset($this->items[$offset]) ? $this->items[$offset] : null; + } + + /** + * Assigns a value to the specified offset. + * + * @param mixed $offset The offset to assign the value to. + * @param mixed $value The value to set. + */ + public function offsetSet($offset, $value) + { + if (is_null($offset)) { + $this->items[] = $value; + } else { + $this->items[$offset] = $value; + } + } + + /** + * Unsets an offset. + * + * @param mixed $offset The offset to unset. + */ + public function offsetUnset($offset) + { + if ($offset == key($this->items)) { + $this->unset = true; + } + unset($this->items[$offset]); + } + + // Implements Countable + + /** + * This method is executed when using the count() function. + * + * @return int The count of items. + */ + public function count() + { + return count($this->items); + } + + // Implements Serializable + + /** + * Returns string representation of the object. + * + * @return string Returns the string representation of the object. + */ + public function serialize() + { + return serialize($this->items); + } + + /** + * Called during unserialization of the object. + * + * @param string $serialized The string representation of the object. + */ + public function unserialize($serialized) + { + $this->items = unserialize($serialized); + } +} diff --git a/system/src/Grav/Common/Page/Asset.php b/system/src/Grav/Common/Page/Asset.php new file mode 100644 index 000000000..148900cf5 --- /dev/null +++ b/system/src/Grav/Common/Page/Asset.php @@ -0,0 +1,321 @@ +get('type') == 'image') { + $filePath = $this->get('path') . '/' . $this->get('filename'); + $image_info = getimagesize($filePath); + $this->set('thumb', $filePath); + $this->def('width', $image_info[0]); + $this->def('height', $image_info[1]); + $this->def('mime', $image_info['mime']); + $this->reset(); + } else { + $this->def('mime', 'application/octet-stream'); + } + } + + /** + * Return string representation of the object (html or url). + * + * @return string + */ + public function __toString() + { + return $this->linkImage ? $this->html() : $this->url(); + } + + /** + * Return URL to file. + * + * @return string + */ + public function url() + { + $config = Registry::get('Config'); + + if ($this->image) { + $output = $this->image->cacheFile($this->type, $this->quality); + $this->reset(); + } else { + $relPath = preg_replace('|^' . ROOT_DIR . '|', '', $this->get('path')); + $output = $relPath . '/' . $this->get('filename'); + } + + return $config->get('system.base_url_relative') . '/'. $output; + } + + /** + * Sets image output format. + * + * @param string $type + * @param int $quality + */ + public function format($type = null, $quality = 80) + { + if (!$this->image) { + $this->image(); + } + + $this->type = $type; + $this->quality = $quality; + } + + /** + * Returns tag from the asset. + * + * @param string $title + * @param string $class + * @param string $type + * @param int $quality + * @return string + */ + public function img($title = null, $class = null, $type = null, $quality = 80) + { + if (!$this->image) { + $this->image(); + } + + $output = $this->html($title, $class, $type, $quality); + + return $output; + } + + /** + * Return HTML markup from the asset. + * + * @param string $title + * @param string $class + * @param string $type + * @param int $quality + * @return string + */ + public function html($title = null, $class = null, $type = null, $quality = 80) + { + $title = $title ? $title : $this->get('title'); + $class = $class ? $class : ''; + + if ($this->image) { + $type = $type ? $type : $this->type; + $quality = $quality ? $quality : $this->quality; + + $url = $this->url($type, $quality); + $this->reset(); + + $output = '' . $title . ''; + } else { + $output = $title; + } + + if ($this->linkTarget) { + $config = Registry::get('Config'); + + $output = 'linkAttributes. ' class="'. $class . '">' . $output . ''; + + $this->linkTarget = $this->linkAttributes = null; + } + + return $output; + } + + /** + * Return lightbox HTML for the asset. + * + * @param int $width + * @param int $height + * @return $this + */ + public function lightbox($width = null, $height = null) + { + $this->linkAttributes = ' rel="lightbox"'; + + return $this->link($width, $height); + } + + /** + * Return link HTML for the asset. + * + * @param int $width + * @param int $height + * @return $this + */ + public function link($width = null, $height = null) + { + if ($this->image) { + $image = clone $this->image; + if ($width && $height) { + $image->cropResize($width, $height); + } + $this->linkTarget = $image->cacheFile($this->type, $this->quality); + } else { + // TODO: we need to find out URI in a bit better way. + $relPath = preg_replace('|^' . ROOT_DIR . '|', '', $this->get('path')); + $this->linkTarget = $relPath. '/' . $this->get('filename'); + } + + return $this; + } + + /** + * Reset image. + * + * @return $this + */ + public function reset() + { + $this->image = null; + + if ($this->get('type') == 'image') { + $this->image(); + $this->filter(); + } + $this->type = 'guess'; + $this->quality = 80; + + 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'; + } + + // Always initialize image. + if (!$this->image) { + $this->image(); + } + $result = call_user_func_array(array($this->image, $method), $args); + + // Returns either current object or result of the action. + return $result instanceof ImageFile ? $this : $result; + } + + /** + * Gets asset image, resets image manipulation operations. + * + * @param string $variable + * @return $this + */ + public function image($variable = 'thumb') + { + // TODO: add default file + $file = $this->get($variable); + $this->image = ImageFile::open($file) + ->setCacheDir(basename(IMAGES_DIR)) + ->setActualCacheDir(IMAGES_DIR) + ->setPrettyName(basename($this->get('basename'))); + + $this->filter(); + + return $this; + } + + /** + * Add meta file for the asset. + * + * @param $type + * @return $this + */ + public function addMetaFile($type) + { + $this->meta[$type] = $type; + + $path = $this->get('path') . '/' . $this->get('filename') . '.meta.' . $type; + if ($type == 'yaml') { + $this->merge(Yaml::instance($path)->content()); + } elseif (in_array($type, array('jpg', 'jpeg', 'png', 'gif'))) { + $this->set('thumb', $path); + } + $this->reset(); + + return $this; + } + + /** + * 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, array()); + foreach ($filters as $params) { + $params = (array) $params; + $method = array_shift($params); + $this->__call($method, $params); + } + } +} diff --git a/system/src/Grav/Common/Page/Assets.php b/system/src/Grav/Common/Page/Assets.php new file mode 100644 index 000000000..f212a7f73 --- /dev/null +++ b/system/src/Grav/Common/Page/Assets.php @@ -0,0 +1,201 @@ +path = $path; + + $iterator = new \DirectoryIterator($path); + + /** @var \DirectoryIterator $info */ + foreach ($iterator as $info) { + // Ignore folders and Markdown files. + if ($info->isDot() || !$info->isFile() || $info->getExtension() == 'md') { + continue; + } + + // Find out the real filename, in case of we are at the metadata. + $filename = $info->getFilename(); + list($basename, $ext, $meta) = $this->getFileParts($filename); + + // Get asset instance creating it if it didn't exist. + $asset = $this->get("{$basename}.{$ext}", true); + if (!$asset) { + continue; + } + + // Assign meta files to the asset. + if ($meta) { + $asset->addMetaFile($meta); + } + } + } + + /** + * Get asset by basename and extension. + * + * @param string $filename + * @param bool $create + * @return Asset|null + */ + public function get($filename, $create = false) + { + if ($create && !isset($this->instances[$filename])) { + $parts = explode('.', $filename); + $ext = array_pop($parts); + $basename = implode('.', $parts); + + /** @var Config $config */ + $config = Registry::get('Config'); + + // Check if asset type has been configured. + $params = $config->get("assets.{$ext}"); + if (!$params) { + return null; + } + + $filePath = $this->path . '/' . $filename; + $params += array( + 'type' => 'file', + 'thumb' => 'assets/thumb.png', + 'mime' => 'application/octet-stream', + 'name' => $filename, + 'filename' => $filename, + 'basename' => $basename, + 'extension' => $ext, + 'path' => $this->path, + 'modified' => filemtime($filePath), + ); + + $lookup = array( + USER_DIR . 'images/', + SYSTEM_DIR . 'images/', + ); + foreach ($lookup as $path) { + if (is_file($path . $params['thumb'])) { + $params['thumb'] = $path . $params['thumb']; + break; + } + } + + $this->add(new Asset($params)); + } + + return isset($this->instances[$filename]) ? $this->instances[$filename] : null; + } + + /** + * Get a list of all assets. + * + * @return array|Asset[] + */ + public function all() + { + return $this->instances; + } + + /** + * Get a list of all image assets. + * + * @return array|Asset[] + */ + public function images() + { + return $this->images; + } + + /** + * Get a list of all video assets. + * + * @return array|Asset[] + */ + public function videos() + { + return $this->videos; + } + + /** + * Get a list of all file assets. + * + * @return array|Asset[] + */ + public function files() + { + return $this->files; + } + + /** + * @internal + */ + protected function add($file) + { + $this->instances[$file->filename] = $file; + switch ($file->type) { + case 'image': + $this->images[$file->filename] = $file; + break; + case 'video': + $this->videos[$file->filename] = $file; + break; + default: + $this->files[$file->filename] = $file; + } + } + + /** + * Get filename, extension and meta part. + * + * @param string $filename + * @return array + */ + protected function getFileParts($filename) + { + $fileParts = explode('.', $filename); + + $name = array_shift($fileParts); + $extension = null; + while (($part = array_shift($fileParts)) !== null) { + if ($part != 'meta') { + if (isset($extension)) { + $name .= '.' . $extension; + } + $extension = $part; + } else { + break; + } + } + $meta = implode('.', $fileParts); + + return array($name, $extension, $meta); + } +} diff --git a/system/src/Grav/Common/Page/Collection.php b/system/src/Grav/Common/Page/Collection.php new file mode 100644 index 000000000..857675af6 --- /dev/null +++ b/system/src/Grav/Common/Page/Collection.php @@ -0,0 +1,187 @@ +params = $params; + $this->pages = $pages ? $pages : Registry::get('Pages'); + } + + public function params() + { + return $this->params; + } + + /** + * 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 + * @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); + } + + /** + * 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 + * + * @return Object the previous item + */ + public function prevSibling($path) + { + return $this->adjacentSibling($path, -1); + } + + /** + * Gets the next sibling based on current position + * + * @return Object the next item + */ + public function nextSibling($path) + { + return $this->adjacentSibling($path, 1); + } + + /** + * Returns the adjacent sibling based on a direction + * @param integer $direction either -1 or +1 + * @return Object the sibling item + */ + public function adjacentSibling($path, $direction = 1) + { + + $values = array_keys($this->items); + $keys = array_flip($values); + $index = $keys[$path] - $direction; + + return isset($values[$index]) ? $this->offsetGet($values[$index]) : $this; + } + + /** + * Returns the item in the current position + * @param String $path the path the item + * @return Object item in the array the the current position + */ + public function currentPosition($path) { + return array_search($path,array_keys($this->items)); + } +} diff --git a/system/src/Grav/Common/Page/Page.php b/system/src/Grav/Common/Page/Page.php new file mode 100644 index 000000000..dfeaac607 --- /dev/null +++ b/system/src/Grav/Common/Page/Page.php @@ -0,0 +1,1588 @@ +folder to get full path. + */ + protected $path; + + protected $parent; + protected $template; + protected $visible; + protected $slug; + protected $route; + protected $routable; + protected $modified; + protected $id; + protected $header; + protected $content; + protected $raw_content; + protected $pagination; + protected $assets; + protected $title; + protected $max_count; + protected $menu; + protected $date; + protected $taxonomy; + protected $order_by; + protected $order_dir; + protected $order_manual; + protected $modular; + protected $modular_twig; + protected $processing; + protected $summary_size; + + /** + * @var Page Unmodified (original) version of the page. Used for copying and moving the page. + */ + private $_original; + + /** + * @var string Action + */ + private $_action; + + /** + * Page Object Constructor + * + * @param array $array An array of existing page objects + */ + public function __construct($array = array()) + { + /** @var Config $config */ + $config = Registry::get('Config'); + + $this->routable = true; + $this->taxonomy = array(); + $this->processing = $config->get('system.pages.process'); + } + + /** + * Initializes the page instance variables based on a file + * + * @param \SplFileInfo $file The file information for the .md file that the page represents + * @return void + */ + public function init($file) + { + $this->filePath($file->getPathName()); + $this->modified(filemtime($file->getPath())); + $this->id($this->modified().md5($this->filePath())); + $this->header(); + $this->slug(); + $this->visible(); + $this->modularTwig($this->slug[0] == '_'); + } + + /** + * 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; + } + return $file->raw(); + } + + /** + * 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) { + $this->raw_content = $file->markdown(); + $this->header = (object) $file->header(); + + $var = true; + } + } + + if ($var) { + if (isset($this->header->slug)) { + $this->slug = trim($this->header->slug); + } + if (isset($this->header->title)) { + $this->title = trim($this->header->title); + } + 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 = $this->header->routable; + } + if (isset($this->header->visible)) { + $this->visible = $this->header->visible; + } + if (isset($this->header->modular)) { + $this->modular = $this->header->modular; + } + 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 = strtotime($this->header->date); + } + if (isset($this->header->taxonomy)) { + foreach ($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->processing)) { + foreach ($this->header->processing as $process => $status) { + $this->processing[$process] = $status; + } + } + } + return $this->header; + } + + /** + * Get the summary. + * + * @param int $size Max summary size. + * @return string + */ + public function summary($size = null) + { + + $content = $this->content(); + + // Return calculated summary based on summary divider's position + if (!$size && isset($this->summary_size)) { + return substr($content, 0, $this->summary_size); + } + + // Return calculated summary based on setting in site config file + /** @var Config $config */ + $config = Registry::get('Config'); + if (!$size && $config->get('site.summary.size')) { + $size = $config->get('site.summary.size'); + } + + // Return calculated summary based on defaults + if (!$size) { + $size = 300; + } + + return Utils::truncateHTML($content, $size); + } + + /** + * 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) { + + // Load cached content + /** @var Cache $cache */ + $cache = Registry::get('Cache'); + $cache_id = md5('page'.$this->id()); + $content = $cache->fetch($cache_id); + + $update_cache = false; + if ($content === false) { + // Process Markdown + $content = $this->processMarkdown(); + $update_cache = true; + } + + // Process Twig if enabled + if ($this->shouldProcess('twig')) { + + // Always process twig if caching in the page is disabled + $process_twig = (isset($this->header->cache_enable) && !$this->header->cache_enable); + + // Do we want to cache markdown, but process twig in each page? + if ($update_cache && $process_twig) { + $cache->save($cache_id, $content); + $update_cache = false; + } + + // Do we need to process twig this time? + if ($update_cache || $process_twig) { + /** @var Twig $twig */ + $twig = Registry::get('Twig'); + $content = $twig->processPage($this, $content); + } + } + + // Cache the whole page, including processed content + if ($update_cache) { + $cache->save($cache_id, $content); + } + + // Handle summary divider + $divider_pos = strpos($content, '

'.SUMMARY_DELIMITER.'

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

'.SUMMARY_DELIMITER.'

', '', $content); + } + + $this->content = $content; + + $this->assets(); + } + + return $this->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 dirname($this->route()); + } + if ($name == 'order') { + $order = $this->order(); + return $order ? (int) $this->order() : ''; + } + if ($name == 'folder') { + $regex = '/^[0-9]+\./u'; + return preg_replace($regex, '', $this->folder); + } + if ($name == 'type') { + return basename($this->name(), '.md'); + } + if ($name == 'assets') { + return $this->assets()->all(); + } + if ($name == 'assets.file') { + return $this->assets()->files(); + } + if ($name == 'assets.video') { + return $this->assets()->videos(); + } + if ($name == 'assets.image') { + return $this->assets()->images(); + } + + $path = explode('.', $name); + $scope = array_shift($path); + + 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; + } + + /** + * Get file object to the page. + * + * @return File\Markdown|null + */ + public function file() + { + if ($this->name) { + return File\Markdown::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->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 Page + */ + public function move(Page $parent) + { + $clone = clone $this; + $clone->_action = 'move'; + $clone->_original = $this; + $clone->parent($parent); + $clone->id(time().md5($clone->filePath())); + // TODO: make sure that the path is in user context. + if ($parent->path()) { + $clone->path($parent->path() . '/' . $clone->folder()); + } + // TODO: make sure we always have the route. + if ($parent->route()) { + $clone->route($parent->route() . '/'. $clone->slug()); + } + + return $clone; + } + + /** + * 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 Page + */ + public function copy($parent) + { + $clone = $this->move($parent); + $clone->_action = 'copy'; + + return $clone; + } + + /** + * Get blueprints for the page. + * + * @return Data\Blueprint + */ + public function blueprints() + { + /** @var Pages $pages */ + $pages = Registry::get('Pages'); + + return $pages->blueprints($this->template()); + } + + /** + * 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()); + $this->header($values['header']); + } + + /** + * Get unknown header variables. + * + * @return array + */ + public function extra() + { + $blueprints = $this->blueprints(); + return $blueprints->extra($this->toArray(), 'header.'); + } + + /** + * Convert page to an array. + * + * @return array + */ + public function toArray() + { + return array( + '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 assets as found in the page folder. + * + * @param Assets $var Representation of associated assets. + * @return Assets Representation of associated assets. + */ + public function assets($var = null) + { + /** @var Cache $cache */ + $cache = Registry::get('Cache'); + + if ($var) { + $this->assets = $var; + } + if ($this->assets === null) { + // Use cached assets if possible. + $assets_cache_id = md5('assets'.$this->id()); + if (!$assets = $cache->fetch($assets_cache_id)) { + $assets = new Assets($this->path()); + $cache->save($assets_cache_id, $assets); + } + $this->assets = $assets; + } + return $this->assets; + } + + /** + * 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 child_type() + { + 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 = str_replace(CONTENT_EXT, '', $this->name()); + } + return $this->template; + } + + /** + * 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; + } + } + return $this->visible; + } + + /** + * Gets and Sets whether or not this Page is routable, ie you can reach it + * via a URL + * + * @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; + } + + /** + * Gets and Sets the processing 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 processing($var = null) + { + if ($var !== null) { + $this->processing = (array) $var; + } + return $this->processing; + } + + /** + * 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; + $baseRoute = $this->parent ? (string) $this->parent()->route() : null; + $this->route = isset($baseRoute) ? $baseRoute . '/'. $this->slug : null; + } + + if (empty($this->slug)) { + $regex = '/^[0-9]+\./u'; + $this->slug = preg_replace($regex, '', $this->folder); + $baseRoute = $this->parent ? (string) $this->parent()->route() : null; + $this->route = isset($baseRoute) ? $baseRoute . '/'. $this->slug : null; + } + return $this->slug; + } + + /** + * Get/set order number of this page. + * + * @param int $var + * @return int|bool + */ + public function order($var = null) + { + $regex = '/^[0-9]+\./u'; + if ($var !== null) { + $order = !empty($var) ? sprintf('%02d.', (int) $var) : ''; + $slug = preg_replace($regex, '', $this->folder); + $this->folder($order.$slug); + } + preg_match($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 + * @return string The url. + */ + public function url($include_host = false) + { + /** @var Uri $uri */ + $uri = Registry::get('Uri'); + $rootUrl = $uri->rootUrl($include_host); + $url = $rootUrl.'/'.trim($this->route(), '/'); + + // trim trailing / if not root + if ($url !== '/') { + $url = rtrim($url, '/'); + } + + return $url; + } + + /** + * Gets the route for the page based on 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; + } + 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 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->name ? $this->path . '/' . $this->folder . '/' . $this->name : null; + } + + /** + * 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 = strtotime($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 = Registry::get('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) + { + if ($var !== null) { + $this->modular = (bool) $var; + } + return $this->modular; + } + + /** + * 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->processing['twig'] = true; + } + } + 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->processing[$process]) ? (bool) $this->processing[$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 !== null) { + $this->parent = $var ? $var->path() : ''; + } + /** @var Pages $pages */ + $pages = Registry::get('Pages'); + + return $pages->get($this->parent); + } + + /** + * Returns children of this page. + * + * @return Collection + */ + public function children() + { + /** @var Pages $pages */ + $pages = Registry::get('Pages'); + + return $pages->children($this->path()); + } + + /** + * @throws \Exception + * @deprecated + */ + public function count() + { + throw new \Exception('Use $page->children()->count() instead.'); + } + + /** + * @param $key + * @throws \Exception + * @deprecated + */ + public function __get($key) + { + throw new \Exception('Use $page->children()->__get() instead.'); + } + + /** + * @param $key + * @param $value + * @throws \Exception + * @deprecated + */ + public function __set($key, $value) + { + throw new \Exception('Use $page->children()->__set() instead.'); + } + + /** + * @throws \Exception + * @deprecated + */ + public function current() + { + throw new \Exception('Use $page->children()->current() instead.'); + } + + /** + * @throws \Exception + * @deprecated + */ + public function next() + { + throw new \Exception('Use $page->children()->next() instead.'); + } + + /** + * @throws \Exception + * @deprecated + */ + public function prev() + { + throw new \Exception('Use $page->children()->prev() instead.'); + } + + /** + * @param string $key + * @throws \Exception + * @deprecated + */ + public function nth($key) + { + throw new \Exception('Use $page->children()->nth($position) instead.'); + } + + /** + * 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() + { + /** @var Pages $pages */ + $pages = Registry::get('Pages'); + $parent = $pages->get($this->parent); + + if ($this->path() == array_values($parent->items)[0]) { + return true; + } else { + return false; + } + } + + /** + * 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() + { + /** @var Pages $pages */ + $pages = Registry::get('Pages'); + $parent = $pages->get($this->parent); + + if ($this->path() == array_values($parent->items)[count($parent->items)-1]) { + return true; + } else { + return false; + } + } + + /** + * 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) + { + /** @var Pages $pages */ + $pages = Registry::get('Pages'); + $parent = $pages->get($this->parent); + $current = $this->slug(); + + $keys = array_flip(array_keys($parent->items)); + $values = array_values($parent->items); + $index = $keys[$current] - $direction; + + return array_key_exists($index, $values) ? $pages->get($values[$index]) : $this; + } + + /** + * 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() + { + /** @var Uri $uri */ + $uri = Registry::get('Uri'); + if ($this->url() == $uri->url()) { + 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 = Registry::get('Uri'); + if (!$this->home() && (strpos($uri->url(), $this->url()) !== false)) { + return true; + } + 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 and !$this->visible) { + return true; + } else { + return false; + } + } + + /** + * Helper method to return a page. + * + * @param string $url the url of the page + * @return Page page you were looking for if it exists + * @deprecated + */ + public function find($url) + { + /** @var Pages $pages */ + $pages = Registry::get('Pages'); + return $pages->dispatch($url); + } + + /** + * Get a collection of pages in the current context. + * + * @param string|array $params + * @return Collection + * @throws \InvalidArgumentException + */ + public function collection($params = 'content') + { + 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 array(); + } + + $collection = $this->evaluate($params['items']); + if (!$collection instanceof Collection) { + $collection = new Collection(); + } + $collection->setParams($params); + + // TODO: MOVE THIS INTO SOMEWHERE ELSE? + /** @var Uri $uri */ + $uri = Registry::get('Uri'); + /** @var Config $config */ + $config = Registry::get('Config'); + + 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) { + if ($page->modular()) { + continue; + } + foreach ($items as $item) { + if (empty($page->taxonomy[$taxonomy]) + || !in_array($item, $page->taxonomy[$taxonomy])) { + $collection->remove(); + } + } + } + + $config->set('system.cache.enabled', false); + } + } + // TODO: END OF MOVE + + 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 = Registry::get('Grav'); + + // New Custom event to handle things like pagination. + $grav->fireEvent('onAfterCollectionProcessed', $collection); + + $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 + */ + protected function evaluate($value) + { + // Parse command. + if (is_string($value)) { + // Format: @command.param + $cmd = $value; + $params = array(); + } elseif (is_array($value) && count($value) == 1) { + // Format: @command.param: { attr1: value1, attr2: value2 } + $cmd = (string) key($value); + $params = (array) current($value); + } else { + return $value; + } + + // We only evaluate commands which start with @ + if (empty($cmd) || $cmd[0] != '@') { + return $value; + } + + $parts = explode('.', $cmd); + $current = array_shift($parts); + + $results = null; + switch ($current) { + case '@self': + if (!empty($parts)) { + switch ($parts[0]) { + case 'modular': + // FIXME: filter by modular + $results = $this->children(); + break; + case 'children': + // FIXME: filter by non-modular + $results = $this->children(); + break; + } + } + 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 = Registry::get('Taxonomy'); + + if (!empty($parts)) { + $params = [implode('.', $parts) => $params]; + } + $results = $taxonomy_map->findTaxonomy($params); + break; + } + + return $results; + } + + /** + * @throws \Exception + * @deprecated + */ + public function subPages() + { + throw new \Exception('Use $page->collection() instead.'); + } + + /** + * Sorting of sub-pages based on how to sort and the order. + * + * default - is the order based on the filesystem, 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 + * modified - is the order based on the last modified date of the pages + * slug - is the order based on the URL slug + * + * @param string $order_by The order by which the sub-pages should be sorted "default", "title", "date", "folder" + * @param string $order_dir The order, either "asc" or "desc" + * @return $this|bool This Page object if sub-pages exist, else false + */ + public function sort($order_by = null, $order_dir = null) + { + throw new \Exception('Use $page->children()->sort() instead.'); + } + + /** + * 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(); + } + + /** + * @throws \Exception + */ + public function hasSubPages() + { + throw new \Exception('Use $page->collection()->count() instead.'); + } + + /** + * Process the Markdown if processing is enabled for it. If not, process as 'raw' which simply strips the + * header YAML from the raw, and sends back the content portion. i.e. the bit below the header. + * + * @return string the content for the page + */ + protected function processMarkdown() + { + // Process Markdown if required + $process_method = $this->shouldProcess('markdown') ? 'parseMarkdownContent' : 'rawContent'; + $content = $this->$process_method($this->raw_content); + + return $content; + } + + /** + * Process the raw content. Basically just strips the headers out and returns the rest. + * + * @param string $content Input raw content + * @return string Output content after headers have been stripped + */ + protected function rawContent($content) + { + return $content; + } + + /** + * Process the Markdown content. This strips the headers, the process the resulting content as Markdown. + * + * @param string $content Input raw content + * @return string Output content that has been processed as Markdown + */ + protected function parseMarkdownContent($content) + { + $parsedown = new \Parsedown(); + $content = $parsedown->parse($content); + return $content; + } + + /** + * 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 + */ + protected function doRelocation($reorder) + { + if (empty($this->_original)) { + return; + } + + // Do reordering. + if ($reorder && $this->order() != $this->_original->order()) { + /** @var Pages $pages */ + $pages = Registry::get('Pages'); + + $parent = $this->parent(); + + // Extract visible children from the parent page. + $visible = array(); + /** @var Page $page */ + foreach ($parent as $page) { + if ($page->order()) { + $visible[$page->slug] = $page->path(); + } + } + + // List only visible pages. + $list = array_intersect($visible, $pages->sort($parent)); + + // 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, array($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->order() != $order+1) { + $page = $page->move($parent); + $page->order($order+1); + $page->save(false); + } + } + } + } + if ($this->_action == 'move' && $this->_original->exists()) { + Folder::move($this->_original->path(), $this->path()); + } + if ($this->_action == 'copy' && $this->_original->exists()) { + Folder::copy($this->_original->path(), $this->path()); + } + + $this->_action = null; + $this->_original = null; + } +} diff --git a/system/src/Grav/Common/Page/Pages.php b/system/src/Grav/Common/Page/Pages.php new file mode 100644 index 000000000..2de156167 --- /dev/null +++ b/system/src/Grav/Common/Page/Pages.php @@ -0,0 +1,547 @@ +grav = Registry::get('Grav'); + $this->config = Registry::get('Config'); + + $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()] = array('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] : array(); + + 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(); + + $lookup = md5(serialize($items)); + 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 + * @return Page + */ + 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] : array(); + return new Collection($children, array(), $this); + } + + /** + * Dispatch URI to a page. + * + * @param $url + * @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; + + // If the page cannot be reached, look into site wide routes. + if (!$all && (!$page || !$page->routable())) { + $route = $this->config->get("site.routes.{$url}"); + if ($route) { + $page = $this->dispatch($route, $all); + } + } + + return $page; + } + + /** + * Get root page. + * + * @return Page + */ + public function root() + { + return $this->instances[rtrim(PAGES_DIR, DS)]; + } + + /** + * Get a blueprint for a page type. + * + * @param string $type + * @return Data\Blueprint + */ + public function blueprints($type) + { + if (!isset($this->blueprints)) { + $this->blueprints = new Data\Blueprints(THEMES_DIR . $this->config->get('system.pages.theme') . '/blueprints/'); + } + + try { + $blueprint = $this->blueprints->get($type); + } catch (\RuntimeException $e) { + $blueprint = $this->blueprints->get('default'); + } + + if (!$blueprint->initialized) { + /** @var Grav $grav */ + $grav = Registry::get('Grav'); + $grav->fireEvent('onCreateBlueprint', $blueprint); + $blueprint->initialized = true; + } + + return $blueprint; + } + + /** + * Get list of route/title of all pages. + * + * @param Page $current + * @param int $level + * @return array + * @throws \RuntimeException + */ + public function getList(Page $current = null, $level = 0) + { + if (!$current) { + if ($level) { + throw new \RuntimeException('Internal error'); + } + + $current = $this->root(); + } + + $list = array(); + if ($current->routable()) { + $list[$current->route()] = str_repeat('  ', ($level-1)*2) . $current->title(); + } + + foreach ($current as $next) { + $list = array_merge($list, $this->getList($next, $level + 1)); + } + + return $list; + } + + /** + * Get available page types. + * + * @return array + */ + static public function types() + { + /** @var Config $config */ + $config = Registry::get('Config'); + $blueprints = new Data\Blueprints(THEMES_DIR . $config->get('system.pages.theme') . '/blueprints/'); + + return $blueprints->types(); + } + + /** + * Get available parents. + * + * @return array + */ + static public function parents() + { + /** @var Pages $pages */ + $pages = Registry::get('Pages'); + return $pages->getList(); + } + + /** + * Builds pages. + * + * @internal + */ + protected function buildPages() + { + $this->sort = array(); + if ($this->config->get('system.cache.enabled')) { + /** @var Cache $cache */ + $cache = Registry::get('Cache'); + /** @var Taxonomy $taxonomy */ + $taxonomy = Registry::get('Taxonomy'); + $last_modified = $this->config->get('system.cache.check.pages', true) + ? Folder::lastModified(PAGES_DIR) : 0; + $page_cache_id = md5(USER_DIR.$last_modified); + + list($this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort) = $cache->fetch($page_cache_id); + if (!$this->instances) { + $this->recurse(); + $this->buildRoutes(); + + // save pages, routes, taxonomy, and sort to cache + $cache->save( + $page_cache_id, + array($this->instances, $this->routes, $this->children, $taxonomy->taxonomy(), $this->sort) + ); + } else { + // If pages was found in cache, set the taxonomy + $taxonomy->taxonomy($taxonomy_map); + } + } else { + $this->recurse(); + $this->buildRoutes(); + } + } + + /** + * Recursive function to load & build page relationships. + * + * @param string $directory + * @param null $parent + * @return Page + * @throws \RuntimeException + * @internal + */ + protected function recurse($directory = PAGES_DIR, &$parent = null) + { + $directory = rtrim($directory, DS); + $iterator = new \DirectoryIterator($directory); + $page = new Page; + + $page->path($directory); + $page->parent($parent); + $page->orderDir($this->config->get('system.pages.order.dir')); + $page->orderBy($this->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()] = array('slug' => $page->slug()); + } + } else { + throw new \RuntimeException('Fatal error when creating page instances.'); + } + + /** @var \DirectoryIterator $file */ + foreach ($iterator as $file) { + $name = $file->getFilename(); + + if ($file->isFile() && Utils::endsWith($name, CONTENT_EXT)) { + + $page->init($file); + + if ($this->config->get('system.pages.events.page')) { + $this->grav->fireEvent('onAfterPageProcessed', $page); + } + + } elseif ($file->isDir() && !$file->isDot()) { + + 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()] = array('slug' => $child->slug()); + + // set the modified time if not already set + if (!$page->date()) { + $page->date($file->getMTime()); + } + + // set the last modified time on pages + $this->lastModified($file->getMTime()); + + if ($this->config->get('system.pages.events.page')) { + $this->grav->fireEvent('onAfterFolderProcessed', $page); + } + } + } + + // 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 = Registry::get('Taxonomy'); + + // Build routes and taxonomy map. + /** @var $page Page */ + foreach ($this->instances as $page) { + + $parent = $page->parent(); + + if ($parent) { + $route = rtrim($parent->route(), '/') . '/' . $page->slug(); + $this->routes[$route] = $page->path(); + $page->route($route); + } + + if (!empty($route)) { + $taxonomy->addTaxonomy($page); + } else { + $page->routable(false); + } + } + + // Alias and set default route to home page. + $home = trim($this->config->get('system.home.alias'), '/'); + 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 = array(); + + 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] = $info['slug']; + break; + case 'basename': + $list[$key] = basename($key); + break; + case 'manual': + case 'default': + default: + $list[$key] = $key; + } + } + + // Sort by the new list. + 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 = array(); + $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]; + // TODO: order by manual needs a hash from the passed variables if we make this more general. + $this->sort[$path][$order_by][$key] = $info; + } + } +} diff --git a/system/src/Grav/Common/Plugin.php b/system/src/Grav/Common/Plugin.php new file mode 100644 index 000000000..fb25dccdb --- /dev/null +++ b/system/src/Grav/Common/Plugin.php @@ -0,0 +1,26 @@ +config = $config; + } +} diff --git a/system/src/Grav/Common/Plugins.php b/system/src/Grav/Common/Plugins.php new file mode 100644 index 000000000..1be3f9e7a --- /dev/null +++ b/system/src/Grav/Common/Plugins.php @@ -0,0 +1,110 @@ +get('plugins'); + + foreach ($plugins as $plugin => $data) { + if (empty($data['enabled'])) { + // Only load enabled plugins. + continue; + } + + $folder = PLUGINS_DIR . $plugin; + $filePath = $folder . DS . $plugin . PLUGIN_EXT; + if (!is_file($filePath)) { + throw new \RuntimeException(sprintf("Plugin '%s' enabled but not found!", $filePath, $plugin)); + } + + require_once $filePath; + + $pluginClass = 'Grav\\Plugin\\'.ucfirst($plugin).'Plugin'; + + if (!class_exists($pluginClass)) { + throw new \RuntimeException(sprintf("Plugin '%s' class not found!", $plugin)); + } + + $this->plugins[$pluginClass] = new $pluginClass($config); + } + + return $this->plugins; + } + + public function add($plugin) + { + if (is_object($plugin)) { + $this->plugins[get_class($plugin)] = $plugin; + } + } + + /** + * Return list of all plugin data with their blueprints. + * + * @return array|Data\Data[] + */ + static public function all() + { + $list = array(); + $iterator = new \DirectoryIterator(PLUGINS_DIR); + + /** @var \DirectoryIterator $directory */ + foreach ($iterator as $directory) { + if (!$directory->isDir() || $directory->isDot()) { + continue; + } + + $type = $directory->getBasename(); + $list[$type] = self::get($type); + } + + ksort($list); + + return $list; + } + + static public function get($type) + { + $blueprints = new Data\Blueprints(PLUGINS_DIR . $type); + $blueprint = $blueprints->get('blueprints'); + $blueprint->name = $type; + + // Load default configuration. + $file = File\Yaml::instance(PLUGINS_DIR . "{$type}/{$type}" . YAML_EXT); + $obj = new Data\Data($file->content(), $blueprint); + + // Override with user configuration. + $file = File\Yaml::instance(USER_DIR . "config/plugins/{$type}" . YAML_EXT); + $obj->merge($file->content()); + + // Save configuration always to user/config. + $obj->file($file); + + return $obj; + } +} diff --git a/system/src/Grav/Common/Registry.php b/system/src/Grav/Common/Registry.php new file mode 100644 index 000000000..59fbe5d7a --- /dev/null +++ b/system/src/Grav/Common/Registry.php @@ -0,0 +1,98 @@ +registry[$key])) { + throw new \Exception("There is no entry for key " . $key); + } + + return self::$instance->registry[$key]; + } + + /** + * @internal + */ + private function __construct() + { + } + + /** + * @internal + */ + private function __clone() + { + } + + /** + * Store entry to the registry. + * + * @param string $key + * @param mixed $value + * @throws \Exception + */ + public function store($key, $value) + { + if (isset($this->registry[$key])) { + throw new \Exception("There is already an entry for key " . $key); + } + + $this->registry[$key] = $value; + } + + /** + * Get entry from the registry. + * + * @param string $key + * @return mixed + * @throws \Exception + */ + public function retrieve($key) + { + if (!isset($this->registry[$key])) { + throw new \Exception("There is no entry for key " . $key); + } + + return $this->registry[$key]; + } +} diff --git a/system/src/Grav/Common/Session/Message.php b/system/src/Grav/Common/Session/Message.php new file mode 100644 index 000000000..1039c527b --- /dev/null +++ b/system/src/Grav/Common/Session/Message.php @@ -0,0 +1,91 @@ + $message, 'scope' => $scope); + + $this->messages[] = $message; + + return $this; + } + + /** + * Clear message queue. + * + * @param string $scope + * @return $this + */ + public function clear($scope = null) + { + if ($scope === null) { + $this->messages = array(); + } else { + foreach ($this->messages as $key => $message) { + if ($message['scope'] == $scope) { + unset($this->messages[$key]); + } + } + } + return $this; + } + + /** + * Fetch all messages. + * + * @param string $scope + * @return array + */ + public function all($scope = null) + { + if ($scope === null) { + return array_values($this->messages); + } + + $messages = array(); + foreach ($this->messages as $message) { + if ($message['scope'] == $scope) { + $messages[] = $message; + } + } + + return $messages; + } + + /** + * Fetch and clear message queue. + * + * @param string $scope + * @return array + */ + public function fetch($scope = null) + { + $messages = $this->all($scope); + $this->clear($scope); + + return $messages; + } + +} diff --git a/system/src/Grav/Common/Session/Session.php b/system/src/Grav/Common/Session/Session.php new file mode 100644 index 000000000..0fe34d7f9 --- /dev/null +++ b/system/src/Grav/Common/Session/Session.php @@ -0,0 +1,245 @@ +started = true; + + return $this; + } + + /** + * Get session ID + * + * @return string Session ID + */ + public function getId() + { + return session_id(); + } + + /** + * Set session Id + * + * @param string $id Session ID + * + * @return $this + */ + public function setId($id) + { + session_id($id); + + return $this; + } + + + /** + * Get session name + * + * @return string + */ + public function getName() + { + return session_name(); + } + + /** + * Set session name + * + * @param string $name + * + * @return $this + */ + public function setName($name) + { + session_name($name); + + return $this; + } + + /** + * Invalidates the current session. + * + * @return $this + */ + public function invalidate() + { + $params = session_get_cookie_params(); + setcookie(session_name(), '', time() - 42000, + $params['path'], $params['domain'], + $params['secure'], $params['httponly'] + ); + + session_unset(); + session_destroy(); + + $this->started = false; + + return $this; + } + + /** + * Force the session to be saved and closed + * + * @return $this + */ + public function close() + { + session_write_close(); + + $this->started = false; + + return $this; + } + + /** + * Checks if an attribute is defined. + * + * @param string $name The attribute name + * + * @return bool True if the attribute is defined, false otherwise + */ + public function __isset($name) + { + return isset($_SESSION[$name]); + } + + /** + * Returns an attribute. + * + * @param string $name The attribute name + * + * @return mixed + */ + public function __get($name) + { + return isset($_SESSION[$name]) ? $_SESSION[$name] : null; + } + + /** + * Sets an attribute. + * + * @param string $name + * @param mixed $value + */ + public function __set($name, $value) + { + $_SESSION[$name] = $value; + } + + /** + * Removes an attribute. + * + * @param string $name + * + * @return mixed The removed value or null when it does not exist + */ + public function __unset($name) + { + unset($_SESSION[$name]); + } + + /** + * Returns attributes. + * + * @return array Attributes + */ + public function all() + { + return $_SESSION; + } + + + /** + * Retrieve an external iterator + * + * @return \ArrayIterator Return an ArrayIterator of $_SESSION + */ + public function getIterator() + { + return new \ArrayIterator($_SESSION); + } + + /** + * Checks if the session was started. + * + * @return Boolean + */ + public function started() + { + return $this->started; + } +} diff --git a/system/src/Grav/Common/Taxonomy.php b/system/src/Grav/Common/Taxonomy.php new file mode 100644 index 000000000..3143e2d4e --- /dev/null +++ b/system/src/Grav/Common/Taxonomy.php @@ -0,0 +1,99 @@ +taxonomy_map = array(); + } + + /** + * Takes an individual page and processes the taxonomies configured in its header. It + * then adds those taxonomies to the map + * + * @param Page\Page $page the page to process + * @param array $page_taxonomy + */ + public function addTaxonomy(Page\Page $page, $page_taxonomy = null) + { + if (!$page_taxonomy) { + $page_taxonomy = $page->taxonomy(); + } + + $config = Registry::get('Config'); + if ($config->get('site.taxonomies') && count($page_taxonomy) > 0) { + foreach ((array) $config->get('site.taxonomies') as $taxonomy) { + if (isset($page_taxonomy[$taxonomy])) { + foreach ((array) $page_taxonomy[$taxonomy] as $item) { + // TODO: move to pages class? + $this->taxonomy_map[$taxonomy][(string) $item][$page->path()] = array('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']] + * @return Page\Page page object with sub-pages set to contain matches found in the taxonomy map + */ + public function findTaxonomy($taxonomies) + { + $results = array(); + + foreach ((array)$taxonomies as $taxonomy => $items) { + foreach ((array) $items as $item) { + if (isset($this->taxonomy_map[$taxonomy][$item])) { + $results = array_merge($results, $this->taxonomy_map[$taxonomy][$item]); + } + } + } + + return new Page\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 000000000..31e5cf477 --- /dev/null +++ b/system/src/Grav/Common/Theme.php @@ -0,0 +1,6 @@ +isDir() || $directory->isDot()) { + continue; + } + + $type = $directory->getBasename(); + $list[$type] = self::get($type); + } + + ksort($list); + + return $list; + } + + /** + * Get theme or throw exception if it cannot be found. + * + * @param string $type + * @return Data\Data + * @throws \RuntimeException + */ + static public function get($type) + { + if (!$type) { + throw new \RuntimeException('Theme name not provided.'); + } + + $blueprints = new Data\Blueprints(THEMES_DIR . $type); + $blueprint = $blueprints->get('blueprints'); + $blueprint->name = $type; + + // Find thumbnail. + $thumb = THEMES_DIR . "{$type}/thumbnail.jpg"; + if (file_exists($thumb)) { + // TODO: use real URL with base path. + $blueprint->set('thumbnail', "/user/themes/{$type}/thumbnail.jpg"); + } + + // Load default configuration. + $file = File\Yaml::instance(THEMES_DIR . "{$type}/{$type}" . YAML_EXT); + $obj = new Data\Data($file->content(), $blueprint); + + // Override with user configuration. + $file = File\Yaml::instance(USER_DIR . "config/themes/{$type}" . YAML_EXT); + $obj->merge($file->content()); + + // Save configuration always to user/config. + $obj->file($file); + + return $obj; + } + + public function load($name = null) + { + if (!$name) { + $config = Registry::get('Config'); + $name = $config->get('system.pages.theme'); + } + + $file = THEMES_DIR . "{$name}/{$name}.php"; + if (file_exists($file)) { + require_once $file; + + $className = '\\Grav\\Theme\\' . ucfirst($name); + + if (class_exists($className)) { + $class = new $className; + } + } + + if (empty($class)) { + $class = new Theme; + } + + return $class; + } +} diff --git a/system/src/Grav/Common/Twig.php b/system/src/Grav/Common/Twig.php new file mode 100644 index 000000000..e90ac9e32 --- /dev/null +++ b/system/src/Grav/Common/Twig.php @@ -0,0 +1,241 @@ +twig)) { + + // get Grav and Config + $this->grav = Registry::get('Grav'); + $this->config = $this->grav->config; + $this->uri = Registry::get('Uri'); + $this->taxonomy = Registry::get('Taxonomy'); + + + $this->twig_paths = array(THEMES_DIR . $this->config->get('system.pages.theme') . '/templates'); + $this->grav->fireEvent('onAfterTwigTemplatesPaths'); + + $this->loader = new \Twig_Loader_Filesystem($this->twig_paths); + $loader_chain = new \Twig_Loader_Chain(array($this->loader, new \Twig_Loader_String())); + + $params = $this->config->get('system.twig'); + if (!empty($params['cache'])) { + $params['cache'] = CACHE_DIR; + } + + $this->twig = new \Twig_Environment($loader_chain, $params); + $this->grav->fireEvent('onAfterTwigInit'); + + // set default date format if set in config + if ($this->config->get('system.pages.dateformat.long')) { + $this->twig->getExtension('core')->setDateFormat($this->config->get('system.pages.dateformat.long')); + } + // enable the debug extension if required + if ($this->config->get('system.twig.debug')) { + $this->twig->addExtension(new \Twig_Extension_Debug()); + } + $this->twig->addExtension(new TwigExtension()); + $this->grav->fireEvent('onAfterTwigExtensions'); + + $baseUrlAbsolute = $this->config->get('system.base_url_absolute'); + $baseUrlRelative = $this->config->get('system.base_url_relative'); + $theme = $this->config->get('system.pages.theme'); + $themeUrl = $baseUrlRelative .'/'. USER_PATH . basename(THEMES_DIR) .'/'. $theme; + + // Set some standard variables for twig + $this->twig_vars = array( + 'config' => $this->config, + 'uri' => $this->uri, + 'base_dir' => rtrim(ROOT_DIR, '/'), + 'base_url_absolute' => $baseUrlAbsolute, + 'base_url_relative' => $baseUrlRelative, + 'theme_dir' => THEMES_DIR . $theme, + 'theme_url' => $themeUrl, + 'site' => $this->config->get('site'), + 'stylesheets' => array(), + 'scripts' => array(), + 'taxonomy' => $this->taxonomy, + ); + + } + } + + /** + * @return \Twig_Environment + */ + public function twig() + { + return $this->twig; + } + + /** + * @return \Twig_Loader_Filesystem + */ + public function loader() + { + return $this->loader; + } + + /** + * 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 \RuntimeException + */ + public function processPage(Page $item, $content = null) + { + $this->init(); + $content = $content !== null ? $content : $item->content(); + + // override the twig header vars for local resolution + $this->grav->fireEvent('onAfterPageTwigVars'); + $twig_vars = $this->twig_vars; + + $twig_vars['page'] = $item; + $twig_vars['assets'] = $item->assets(); + $twig_vars['header'] = $item->header(); + + // Get Twig template layout + if ($item->modularTwig()) { + $twig_vars['content'] = $content; + // FIXME: this is inconsistent with main page. + $template = $this->template('modular/' . $item->template()) . TEMPLATE_EXT; + $output = $this->twig->render($template, $twig_vars); + + if ($template == $output) { + throw new \RuntimeException("Template file '{$template}' cannot be found.", 404); + } + } else { + $output = $this->twig->render($content, $twig_vars); + } + + return $output; + } + + /** + * @param string $string string to render. + * @param array $vars Optional variables + * @return string + */ + public function processString($string, array $vars = array()) + { + $this->init(); + + // override the twig header vars for local resolution + $this->grav->fireEvent('onAfterStringTwigVars'); + $vars += $this->twig_vars; + + return $this->twig->render($string, $vars); + } + + /** + * 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) + { + $this->init(); + + // set the page now its been processed + $this->grav->fireEvent('onAfterSiteTwigVars'); + $twig_vars = $this->twig_vars; + $pages = $this->grav->pages; + $page = $this->grav->page; + + $twig_vars['pages'] = $pages->root(); + $twig_vars['page'] = $page; + $twig_vars['header'] = $page->header(); + $twig_vars['content'] = $page->content(); + $ext = '.' . ($format ? $format : 'html') . TWIG_EXT; + + // Get Twig template layout + $template = $this->template($page->template() . $ext); + $output = $this->twig->render($template, $twig_vars); + + if ($template == $output) { + throw new \RuntimeException("Template file '{$template}' cannot be found.", 404); + } + + 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/TwigExtension.php b/system/src/Grav/Common/TwigExtension.php new file mode 100644 index 000000000..a7a9799c1 --- /dev/null +++ b/system/src/Grav/Common/TwigExtension.php @@ -0,0 +1,232 @@ +'.$text.''; + } elseif ($count < 40) { + return ''.$text.''; + } else { + return $text; + } + } + + /** + * Remove disabled objects from the array. If input isn't array, do nothing. + * + * @param array $original + * @return array + */ + public function removeDisabledFilter($original) + { + if (!is_array($original)) { + return $original; + } + $new = array(); + + foreach ($original as $entry) { + if (is_object($entry) && !isset($entry->disabled)) { + $new[] = $entry; + } + } + return $new; + } + + /** + * 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 = array(); + $random = array_slice($original, $offset); + shuffle($random); + + for ($x=0; $x < sizeof($original); $x++) { + if ($x < $offset) { + $sorted[] = $original[$x]; + } else { + $sorted[] = array_shift($random); + } + } + return $sorted; + } + + /** + * 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) + { + // TODO: check this and fix the docblock if needed. + $action = $action.'ize'; + + if (in_array( + $action, + array('titleize','camelize','underscorize','hyphenize', 'humanize','ordinalize','monthize') + )) { + return Inflector::$action($data); + } elseif (in_array($action, array('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); + } + + /** + * Repeat given string x times. + * + * @param string $input + * @param int $multiplier + * @return string + */ + public function repeatFunc($input, $multiplier) + { + return str_repeat($input, $multiplier); + } +} diff --git a/system/src/Grav/Common/Uri.php b/system/src/Grav/Common/Uri.php new file mode 100644 index 000000000..24e950846 --- /dev/null +++ b/system/src/Grav/Common/Uri.php @@ -0,0 +1,287 @@ +base = $base; + $this->root = $base . rtrim(substr($_SERVER['PHP_SELF'], 0, strpos($_SERVER['PHP_SELF'], 'index.php')), '/'); + $this->url = $base . $uri; + + $this->init(); + + } + + /** + * Initializes the URI object based on the url set on the object + */ + public function init() + { + // get any params and remove them + $uri = str_replace($this->root, '', $this->url); + + $this->params = array(); + if (strpos($uri, ':')) { + $bits = explode('/', $uri); + $path = array(); + foreach ($bits as $bit) { + if (strpos($bit, ':') !== false) { + $param = explode(':', $bit); + if (count($param) == 2) { + $this->params[$param[0]] = str_replace('%7C', '/', $param[1]); + } + } else { + $path[] = $bit; + } + } + $uri = implode('/', $path); + } + + // remove the extension if there is one set + $parts = pathinfo($uri); + if (strpos($parts['basename'], '.')) { + $uri = rtrim($parts['dirname'], '/').'/'.$parts['filename']; + $this->extension = $parts['extension']; + } + + // set the new url + $this->url = $this->root . $uri; + + // split into bits + $this->bits = parse_url($uri); + + $this->query = array(); + if (isset($this->bits['query'])) { + parse_str($this->bits['query'], $this->query); + } + + $this->paths = array(); + $this->path = $this->bits['path']; + $this->content_path = trim(str_replace($this->base, '', $this->path), '/'); + if ($this->content_path != '') { + $this->paths = explode('/', $this->content_path); + } + } + + /** + * Return URI path. + * + * @param string $id + * @return string + */ + public function paths($id = null) + { + if (isset($id)) { + return $this->paths[$id]; + } else { + return implode('/', $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 ($absolute ? $this->rootUrl($domain) : '') . '/' . implode('/', $this->paths); + } + + /** + * Return full query string or a single query attribute. + * + * @param string $id Optional attribute. + * @return string + */ + public function query($id = null) + { + if (isset($id)) { + return $this->query[$id]; + } else { + return http_build_query($this->query); + } + } + + /** + * Return all or a single query parameter as a URI compatible string. + * + * @param string $id Optional parameter name. + * @return null|string + */ + public function params($id = null) + { + $params = null; + if ($id === null) { + $output = array(); + foreach ($this->params as $key => $value) { + $output[] = $key . ':' . $value; + $params = '/'.implode('/', $output); + } + } elseif (isset($this->params[$id])) { + $params = "/{$id}:".$this->params[$id]; + } + + return $params; + } + + /** + * Get URI parameter. + * + * @param string $id + * @return bool|string + */ + public function param($id) + { + if (isset($this->params[$id])) { + return urldecode($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() { + return $this->path; + } + + /** + * Return the Extension of the URI + * + * @return String The extension of the URI + */ + public function extension() { + return $this->extension; + } + + /** + * Return the host of the URI + * + * @return String The host of the URI + */ + public function host() { + return $this->host; + } + + /** + * 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)); + } +} diff --git a/system/src/Grav/Common/User/Authentication.php b/system/src/Grav/Common/User/Authentication.php new file mode 100644 index 000000000..3a7af6d8f --- /dev/null +++ b/system/src/Grav/Common/User/Authentication.php @@ -0,0 +1,46 @@ +password); + + // Password needs to be updated, save the file. + if ($result == 2) { + $this->password = Authentication::create($password); + $this->save(); + } + + return (bool) $result; + } + + /** + * Checks user authorisation to the action. + * + * @param string $action + * @return bool + */ + public function authorise($action) + { + return $this->get("access.{$action}") === true; + } +} diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php new file mode 100644 index 000000000..09e564ebd --- /dev/null +++ b/system/src/Grav/Common/Utils.php @@ -0,0 +1,142 @@ +/', '', $text)) <= $length) { + return $text; + } + // splits all html-tags to scanable lines + preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER); + $total_length = strlen($ending); + $open_tags = array(); + $truncate = ''; + foreach ($lines as $line_matchings) { + // if there is any html-tag in this line, handle it and add it (uncounted) to the output + if (!empty($line_matchings[1])) { + // if it's an "empty element" with or without xhtml-conform closing slash + if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) { + // do nothing + // if tag is a closing tag + } else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) { + // delete tag from $open_tags list + $pos = array_search($tag_matchings[1], $open_tags); + if ($pos !== false) { + unset($open_tags[$pos]); + } + // if tag is an opening tag + } else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) { + // add tag to the beginning of $open_tags list + array_unshift($open_tags, strtolower($tag_matchings[1])); + } + // add html-tag to $truncate'd text + $truncate .= $line_matchings[1]; + } + // calculate the length of the plain text part of the line; handle entities as one character + $content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', ' ', $line_matchings[2])); + if ($total_length+$content_length> $length) { + // the number of characters which are left + $left = $length - $total_length; + $entities_length = 0; + // search for html entities + if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', $line_matchings[2], $entities, PREG_OFFSET_CAPTURE)) { + // calculate the real length of all entities in the legal range + foreach ($entities[0] as $entity) { + if ($entity[1]+1-$entities_length <= $left) { + $left--; + $entities_length += strlen($entity[0]); + } else { + // no more characters left + break; + } + } + } + $truncate .= substr($line_matchings[2], 0, $left+$entities_length); + // maximum lenght is reached, so get off the loop + break; + } else { + $truncate .= $line_matchings[2]; + $total_length += $content_length; + } + // if the maximum length is reached, get off the loop + if($total_length>= $length) { + break; + } + } + } else { + if (strlen($text) <= $length) { + return $text; + } else { + $truncate = substr($text, 0, $length - strlen($ending)); + } + } + // if the words shouldn't be cut in the middle... + if (!$exact) { + // ...search the last occurance of a space... + $spacepos = strrpos($truncate, ' '); + if (isset($spacepos)) { + // ...and cut the text in this position + $truncate = substr($truncate, 0, $spacepos); + } + } + // add the defined ending to the text + $truncate .= $ending; + if($considerHtml) { + // close all unclosed html-tags + foreach ($open_tags as $tag) { + $truncate .= ''; + } + } + return $truncate; + } +} diff --git a/system/src/Grav/Console/InstallCommand.php b/system/src/Grav/Console/InstallCommand.php new file mode 100644 index 000000000..f3dd5ba0d --- /dev/null +++ b/system/src/Grav/Console/InstallCommand.php @@ -0,0 +1,283 @@ + '/index.php', + '/composer.json' => '/composer.json', + '/bin' => '/bin', + '/system' => '/system', + '/vendor' => '/vendor', + '/user/plugins/error' => '/user/plugins/error', + '/user/plugins/problems' => '/user/plugins/problems', + '/user/themes/antimatter' => '/user/themes/antimatter', + ); + + 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('install') + ->setDescription('Installs the base Grav system') + ->addArgument( + 'destination', + InputArgument::REQUIRED, + 'The destination directory to symlink into' + ) + ->addOption( + 'symlink', + 's', + InputOption::VALUE_NONE, + 'Symlink the base grav system' + ) + ->setHelp(<<install command help create a development environment that uses symbolic links to link the core of grav to the git cloned repository +EOT + ); + $this->source = getcwd(); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->destination = $input->getArgument('destination'); + + // Create a red output option + $output->getFormatter()->setStyle('red', new OutputFormatterStyle('red')); + $output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan')); + $output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta')); + + // Symlink the Core Stuff + if ($input->getOption('symlink')) { + // Create Some core stuff if it doesn't exist + $this->createDirectories($output); + + // Loop through the symlink mappings and create the symlinks + $this->symlink($output); + + // Copy the Core STuff + } else { + $options = true; + // Create Some core stuff if it doesn't exist + $this->createDirectories($output); + + // Loop through the symlink mappings and copy what otherwise would be symlinks + $this->copy($output); + } + + $this->pages($output); + $this->initFiles($output); + $this->perms($output); + } + + private function createDirectories($output) + { + $output->writeln(''); + $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; + $output->writeln(' ' . $dir . ''); + mkdir($this->destination . $dir, 0777, true); + } + } + + if (!$dirs_created) { + $output->writeln(' Directories already exist'); + } + } + + private function copy($output) + { + $output->writeln(''); + $output->writeln('Copying Files'); + + + foreach ($this->mappings as $source => $target) { + if ((int) $source == $source) { + $source = $target; + } + + $from = $this->source . $source; + $to = $this->destination . $target; + + $output->writeln(' ' . $source . ' -> ' . $to); + $this->rcopy($from, $to); + } + } + + private function symlink($output) + { + $output->writeln(''); + $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; + + $output->writeln(' ' . $source . ' -> ' . $to); + @unlink ($to); + symlink ($from, $to); + } + } + + private function initFiles($output) + { + $this->check($output); + + $output->writeln(''); + $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); + $output->writeln(' '.$target.' -> Created'); + } + } + + if (!$files_init) { + $output->writeln(' Files already exist'); + } + + + } + + private function pages($output) + { + $output->writeln(''); + $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'; + $this->rcopy($destination, $pages_dir); + $output->writeln(' '.$destination.' -> Created'); + + } + } + + private function perms($output) + { + $output->writeln(''); + $output->writeln('Permisions Initializing'); + + $dir_perms = 0755; + + // get pages files and initialize if no pages exist + chmod($this->destination.'/bin/grav', $dir_perms); + $output->writeln(' bin/grav permissions reset to '. decoct($dir_perms)); + } + + + private function check($output) + { + $success = true; + + if (!file_exists($this->destination)) { + $output->writeln(' file: $this->destination does not exist!'); + $success = false; + } + + foreach ($this->directories as $dir) { + if (!file_exists($this->destination . $dir)) { + $output->writeln(' directory: ' . $dir . ' does not exist!'); + $success = false; + } + } + + foreach ($this->mappings as $target => $link) { + if (!file_exists($this->destination . $target)) { + $output->writeln(' mappings: ' . $target . ' does not exist!'); + $success = false; + } + } + if (!$success) { + $output->writeln(''); + $output->writeln('install should be run with --symlink|--s to symlink first'); + exit; + } + } + + private function rcopy($src, $dest){ + + // If the src is not a directory do a simple file copy + if(!is_dir($src)) { + copy($src, $dest); + return true; + } + + // If the destination directory does not exist create it + if(!is_dir($dest)) { + if(!mkdir($dest)) { + // If the destination directory could not be created stop processing + return false; + } + } + + // Open the source directory to read in files + $i = new \DirectoryIterator($src); + foreach($i as $f) { + if($f->isFile()) { + copy($f->getRealPath(), "$dest/" . $f->getFilename()); + } else if(!$f->isDot() && $f->isDir()) { + $this->rcopy($f->getRealPath(), "$dest/$f"); + } + } + } +} diff --git a/system/src/Grav/Console/PackageCommand.php b/system/src/Grav/Console/PackageCommand.php new file mode 100644 index 000000000..fded91ee9 --- /dev/null +++ b/system/src/Grav/Console/PackageCommand.php @@ -0,0 +1,167 @@ +setName("package") + ->setDescription("Handles packaging chores for Grav") + ->addOption( + 'clean', + 'c', + InputOption::VALUE_NONE, + 'Clean out extra files in vendor folder' + ) + ->setHelp('The package command does things and stuff'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + + + // Create a red output option + $output->getFormatter()->setStyle('red', new OutputFormatterStyle('red')); + $output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan')); + $output->getFormatter()->setStyle('green', new OutputFormatterStyle('green')); + $output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta')); + + if ($input->getOption('clean')) { + $this->cleanPaths($output); + } + + } + + // loops over the array of paths and deletes the files/folders + private function cleanPaths($output) + { + $output->writeln(''); + $output->writeln('DELETING'); + + foreach($this->paths_to_remove as $path) { + $path = ROOT_DIR . $path; + + if (is_dir($path) && @$this->rrmdir($path)) { + $output->writeln('dir: ' . $path); + } elseif (is_file($path) && @unlink($path)) { + $output->writeln('file: ' . $path); + } + } + + $output->writeln(''); + $output->writeln('CREATING'); + + foreach($this->paths_to_create as $path) { + $path = ROOT_DIR . $path; + if (@mkdir($path)) { + $output->writeln('dir: ' . $path); + } + } + } + + // Recursively Delete folder - DANGEROUS! USE WITH CARE!!!! + private function rrmdir($dir) { + if (is_dir($dir)) { + $objects = scandir($dir); + foreach ($objects as $object) { + if ($object != "." && $object != "..") { + if (filetype($dir."/".$object) == "dir") $this->rrmdir($dir."/".$object); else unlink($dir."/".$object); + } + } + reset($objects); + rmdir($dir); + return true; + } + } +} diff --git a/user/config/site.yaml b/user/config/site.yaml new file mode 100644 index 000000000..dc52b4484 --- /dev/null +++ b/user/config/site.yaml @@ -0,0 +1,6 @@ +title: Grav +author: + name: Joe Bloggs + email: 'joe@test.com' +description: 'Grav is an easy to use, yet powerful, open source flat-file CMS' + diff --git a/user/config/system.yaml b/user/config/system.yaml new file mode 100644 index 000000000..e05c34ba0 --- /dev/null +++ b/user/config/system.yaml @@ -0,0 +1,31 @@ +home: + alias: '/home' + +pages: + theme: antimatter + process: + markdown: true + twig: false + events: + page: false + twig: true + +cache: + enabled: true + check: + pages: true + driver: auto + prefix: 'g' + +twig: + cache: true + debug: true + auto_reload: true + autoescape: false + +debugger: + enabled: true + max_depth: 10 + log: + enabled: false + timing: false diff --git a/user/pages/01.home/default.md b/user/pages/01.home/default.md new file mode 100644 index 000000000..64ea8aeba --- /dev/null +++ b/user/pages/01.home/default.md @@ -0,0 +1,39 @@ +--- +title: Home +--- + +# Grav is Running! +## You have installed **Grav** successfully + +Congratulations! You have installed the **Base Grav Package** that provides a **simple page** and the default **antimatter** theme to get you started. + +>>>>> If you want a more **full-featured** base install, you should check out [**Skeleton** packages available in the downloads](http://getgrav.org/downloads). + +### Find out all about Grav + +* Learn about **Grav** by checking out our dedicated [Learn Grav](http://learn.getgrav.org) site. +* Download **plugins**, **themes**, as well as other Grav **skeleton** packages from the [Grav Downloads](http://getgrav.org/downloads) page. +* Check out our [Grav Development Blog](http://getgrav.org/blog) to find out the latest goings on in the Grav-verse. + +### Edit this Page + +To edit this page, simply navigate to the folder you installed **Grav** into, and then browse to the `user/pages/01.home` folder and open the `default.md` file in your [editor of choice](http://learn.getgrav.org/basics/requirements). You will see the content of this page in [Markdown format](http://learn.getgrav.org/content/markdown). + +### Create a New Page + +Creating a new page is a simple affair in **Grav**. Simply follow these simple steps: + +1. Navigate to your pages folder: `user/pages/` and create a new folder. In this example, we will use [explicit default ordering](http://learn.getgrav.org/content/content-pages) and call the folder `02.mypage`. +2. Launch your text editor and paste in the following sample code: + + --- + title: My New Page + --- + # My New Page! + + This is the body of **my new page** and I can easily use _Markdown_ syntax here. + +3. Save this file in the `user/pages/02.mypage/` folder as `default.md`. This will tell **Grav** to render the page using the **default** template. +4. That is it! Reload your browser to see your new page in the menu. + +>>> NOTE: The page will automatically show up in the Menu after the "Home" menu item. If you wish to change the name that shows up in the Menu, simple add: `menu: My Page` between the dashes in the page content. This is called the YAML front matter, and it is where you configure page-specific options. diff --git a/user/plugins/error/blueprints.yaml b/user/plugins/error/blueprints.yaml new file mode 100644 index 000000000..2d9bb5adc --- /dev/null +++ b/user/plugins/error/blueprints.yaml @@ -0,0 +1,16 @@ +name: Error Page +version: 1.0.0 +description: Displays error page. + +form: + fields: + enabled: + type: toggle + label: Plugin status + highlight: 1 + default: 0 + options: + 1: Enabled + 0: Disabled + validate: + type: bool diff --git a/user/plugins/error/error.php b/user/plugins/error/error.php new file mode 100644 index 000000000..9c80731a7 --- /dev/null +++ b/user/plugins/error/error.php @@ -0,0 +1,46 @@ +page || !$grav->page->routable())) { + + // try to load user error page + $page = $pages->dispatch($this->config->get('error.404', '/error'), true); + + // if none provided use built in + if (!$page) { + $page = new Page; + $page->init(new \SplFileInfo(__DIR__ . '/pages/error.md')); + } + + // Set the page + $grav->page = $page; + } + } + + /** + * Add current directory to twig lookup paths. + */ + public function onAfterTwigTemplatesPaths() + { + Registry::get('Twig')->twig_paths[] = __DIR__ . '/templates'; + } +} diff --git a/user/plugins/error/error.yaml b/user/plugins/error/error.yaml new file mode 100644 index 000000000..9752cae3f --- /dev/null +++ b/user/plugins/error/error.yaml @@ -0,0 +1,6 @@ +name: 404 Not Found +description: 404 Not found + +enabled: true +routes: + 404: '/error' diff --git a/user/plugins/error/pages/error.md b/user/plugins/error/pages/error.md new file mode 100644 index 000000000..129137fb9 --- /dev/null +++ b/user/plugins/error/pages/error.md @@ -0,0 +1,8 @@ +--- +title: Error Page +robots: noindex,nofollow +template: error +routable: false +code: 404 +--- +Woops. Looks like this page doesn't exist. diff --git a/user/plugins/error/templates/error.html.twig b/user/plugins/error/templates/error.html.twig new file mode 100644 index 000000000..02dab9473 --- /dev/null +++ b/user/plugins/error/templates/error.html.twig @@ -0,0 +1,3 @@ +

Error {{ page.header.code }}

+ +

{{ page.content }}

diff --git a/user/plugins/problems/problems.css b/user/plugins/problems/problems.css new file mode 100644 index 000000000..96bd66437 --- /dev/null +++ b/user/plugins/problems/problems.css @@ -0,0 +1,61 @@ +ul.problems { + list-style: none; + padding: 0; + margin-top: 3rem; +} + +ul.problems li { + margin-bottom: 1rem; + padding: 1rem; +} + +ul.problems li.success { + background: #F1F9F1; + border-left: 5px solid #5CB85C; + color: #3d8b3d; +} + +ul.problems li.error { + background: #FDF7F7; + border-left: 5px solid #D9534F; + color: #b52b27; +} + +ul.problems .fa { + font-size: 3rem; + vertical-align: middle; + margin-left: 1rem; + display: block; + float: left; +} + +ul.problems p { + display: block; + margin: 0.5rem 0.5rem 0.5rem 5rem; +} + +.button.big { + font-size: 1.2rem; +} + +.center { + text-align: center; +} + +.underline { + text-decoration: underline; +} + +.clearfix:after { + visibility: hidden; + display: block; + font-size: 0; + content: " "; + clear: both; + height: 0; + } +.clearfix { display: inline-block; } +/* start commented backslash hack \*/ +* html .clearfix { height: 1%; } +.clearfix { display: block; } +/* close commented backslash hack */ diff --git a/user/plugins/problems/problems.php b/user/plugins/problems/problems.php new file mode 100644 index 000000000..aff1a15d2 --- /dev/null +++ b/user/plugins/problems/problems.php @@ -0,0 +1,173 @@ +check = CACHE_DIR . $validated_prefix .$cache->getKey(); + + if(!file_exists($this->check)) { + + // Run through potential issues + $this->active = $this->problemChecker(); + + // If no issues remain, save a state file in the cache + if (!$this->active) { + // delete any exising validated files + foreach (glob(CACHE_DIR . $validated_prefix. '*') as $filename) { + unlink($filename); + } + + // create a file in the cache dir so it only runs on cache changes + touch($this->check); + } + + } + + + } + + public function onAfterGetPage() + { + if (!$this->active) { + return; + } + + /** @var Grav $grav */ + $grav = Registry::get('Grav'); + $grav->page->content("# Issues Found\n##Please **Review** and **Resolve** before continuing..."); + + } + + /** + * Add current directory to twig lookup paths. + */ + public function onAfterTwigTemplatesPaths() + { + if (!$this->active) { + return; + } + + Registry::get('Twig')->twig_paths[] = __DIR__ . '/templates'; + } + + /** + * Set needed variables to display the problems. + */ + public function onAfterSiteTwigVars() + { + if (!$this->active) { + return; + } + + $twig = Registry::get('Twig'); + $twig->template = 'problems.html.twig'; + $twig->twig_vars['results'] = $this->results; + + if ($this->config->get('plugins.problems.built_in_css')) { + $twig->twig_vars['stylesheets'][] = 'user/plugins/problems/problems.css'; + } + } + + protected function problemChecker() + { + $min_php_version = '5.4.0'; + $problems_found = false; + + + $essential_files = [ + 'index.php' => false, + '.htaccess' => false, + 'cache' => true, + 'logs' => true, + 'images' => true, + 'system' => false, + 'user/data' => true, + 'user/pages' => false, + 'user/config' => false, + 'user/plugins/error' => false, + 'user/plugins' => false, + 'user/themes' => false, + 'vendor' => false + ]; + + // Check PHP version + if (version_compare(phpversion(), '5.4.0', '<')) { + $problems_found = true; + $php_version_adjective = 'lower'; + $php_version_status = false; + + } else { + $php_version_adjective = 'greater'; + $php_version_status = true; + } + $this->results['php'] = [$php_version_status => 'Your PHP version (' . phpversion() . ') is '. $php_version_adjective . ' than the minimum required: ' . $min_php_version . '']; + + // Check for GD library + if (defined('GD_VERSION') && function_exists('gd_info')) { + $gd_adjective = ''; + $gd_status = true; + } else { + $problems_found = true; + $gd_adjective = 'not '; + $gd_status = false; + } + $this->results['gd'] = [$gd_status => 'PHP GD (Image Manipulation Library) is '. $gd_adjective . 'installed']; + + // Check for essential files & perms + $file_problems = []; + foreach($essential_files as $file => $check_writable) { + $file_path = ROOT_DIR . $file; + if (!file_exists($file_path)) { + $problems_found = true; + $file_status = false; + $file_adjective = 'does not exist'; + + } else { + $file_status = true; + $file_adjective = 'exists'; + $is_writeable = is_writable($file_path); + $is_dir = is_dir($file_path); + + if ($check_writable) { + if (!$is_writeable) { + $file_status = false; + $problems_found = true; + $file_adjective .= ' but is not writeable'; + } else { + $file_adjective .= ' and is writeable'; + } + } + } + if (!$file_status || $is_dir || $check_writable) { + $file_problems[$file_path] = [$file_status => $file_adjective]; + } + } + if (sizeof($file_problems) > 0) { + + $this->results['files'] = $file_problems; + } + + return $problems_found; + } +} diff --git a/user/plugins/problems/problems.yaml b/user/plugins/problems/problems.yaml new file mode 100644 index 000000000..1ab22e745 --- /dev/null +++ b/user/plugins/problems/problems.yaml @@ -0,0 +1,2 @@ +enabled: true +built_in_css: true diff --git a/user/plugins/problems/templates/problems.html.twig b/user/plugins/problems/templates/problems.html.twig new file mode 100644 index 000000000..3d16dc24b --- /dev/null +++ b/user/plugins/problems/templates/problems.html.twig @@ -0,0 +1,55 @@ +{% extends 'partials/base.html.twig' %} + +{% block content %} + {{ page.content }} + +

+ Reload Page +

+ +