From a58748f0cd159f21363c8e06729f0dfdedfc3820 Mon Sep 17 00:00:00 2001 From: Angle Date: Fri, 16 Jan 2026 16:29:06 +0100 Subject: [PATCH] premier commit --- .dependencies | 34 + .editorconfig | 17 + .github/FUNDING.yml | 8 + .github/workflows/build.yaml | 81 + .github/workflows/tests.yaml | 68 + .github/workflows/trigger-skeletons.yml | 48 + .gitignore | 51 + .htaccess | 78 + .phan/config.php | 44 + .phan/internal_stubs/Redis.phan_php | 5153 +++++++++++++ .phan/internal_stubs/memcache.phan_php | 460 ++ .phan/internal_stubs/memcached.phan_php | 1308 ++++ .travis.yml | 96 + CHANGELOG.md | 4214 +++++++++++ CODE_OF_CONDUCT.md | 133 + CONTRIBUTING.md | 138 + LICENSE.txt | 21 + README.md | 156 + SECURITY.md | 38 + assets/.gitkeep | 1 + backup/.gitkeep | 1 + bin/composer.phar | Bin 0 -> 2977479 bytes bin/gpm | 46 + bin/grav | 42 + bin/plugin | 43 + cache/.gitkeep | 1 + codeception.yml | 16 + composer.json | 127 + composer.lock | 6549 +++++++++++++++++ images/.gitkeep | 1 + index.php | 51 + logs/.gitkeep | 1 + now.json | 4 + robots.txt | 21 + system/assets/debugger/clockwork.css | 61 + system/assets/debugger/clockwork.js | 37 + system/assets/debugger/phpdebugbar.css | 67 + system/assets/grav.png | Bin 0 -> 1612 bytes system/assets/jquery/jquery-2.1.4.min.js | 4 + system/assets/jquery/jquery-2.x.min.js | 4 + system/assets/jquery/jquery-3.x.min.js | 2 + system/assets/responsive-overlays/1x.png | Bin 0 -> 3238 bytes system/assets/responsive-overlays/2x.png | Bin 0 -> 7593 bytes system/assets/responsive-overlays/3x.png | Bin 0 -> 13002 bytes system/assets/responsive-overlays/4x.png | Bin 0 -> 15545 bytes system/assets/responsive-overlays/unknown.png | Bin 0 -> 5241 bytes system/assets/whoops.css | 19 + system/blueprints/config/backups.yaml | 125 + system/blueprints/config/media.yaml | 5 + system/blueprints/config/scheduler.yaml | 792 ++ system/blueprints/config/security.yaml | 119 + system/blueprints/config/site.yaml | 124 + system/blueprints/config/streams.yaml | 8 + system/blueprints/config/system.yaml | 1917 +++++ system/blueprints/flex/accounts.yaml | 8 + system/blueprints/flex/configure/compat.yaml | 17 + system/blueprints/flex/pages.yaml | 212 + system/blueprints/flex/shared/configure.yaml | 70 + system/blueprints/flex/user-accounts.yaml | 155 + system/blueprints/flex/user-groups.yaml | 124 + system/blueprints/pages/default.yaml | 381 + system/blueprints/pages/external.yaml | 52 + system/blueprints/pages/modular.yaml | 36 + .../blueprints/pages/partials/security.yaml | 67 + system/blueprints/pages/root.yaml | 16 + system/blueprints/user/account.yaml | 157 + system/blueprints/user/account_new.yaml | 18 + system/blueprints/user/group.yaml | 55 + system/blueprints/user/group_new.yaml | 23 + system/config/backups.yaml | 15 + system/config/media.yaml | 223 + system/config/mime.yaml | 1986 +++++ system/config/permissions.yaml | 53 + system/config/scheduler.yaml | 68 + system/config/security.yaml | 47 + system/config/site.yaml | 35 + system/config/system.yaml | 235 + system/defines.php | 104 + system/images/media/thumb-3dm.png | Bin 0 -> 3013 bytes system/images/media/thumb-3ds.png | Bin 0 -> 3116 bytes system/images/media/thumb-3g2.png | Bin 0 -> 3006 bytes system/images/media/thumb-3gp.png | Bin 0 -> 2853 bytes system/images/media/thumb-7z.png | Bin 0 -> 1648 bytes system/images/media/thumb-aac.png | Bin 0 -> 3218 bytes system/images/media/thumb-ai.png | Bin 0 -> 1595 bytes system/images/media/thumb-aif.png | Bin 0 -> 1691 bytes system/images/media/thumb-apk.png | Bin 0 -> 2557 bytes system/images/media/thumb-app.png | Bin 0 -> 2360 bytes system/images/media/thumb-asf.png | Bin 0 -> 2635 bytes system/images/media/thumb-asp.png | Bin 0 -> 2913 bytes system/images/media/thumb-aspx.png | Bin 0 -> 3726 bytes system/images/media/thumb-asx.png | Bin 0 -> 3287 bytes system/images/media/thumb-avi.png | Bin 0 -> 2448 bytes system/images/media/thumb-bak.png | Bin 0 -> 2814 bytes system/images/media/thumb-bat.png | Bin 0 -> 2182 bytes system/images/media/thumb-bin.png | Bin 0 -> 1841 bytes system/images/media/thumb-bmp.png | Bin 0 -> 2573 bytes system/images/media/thumb-cab.png | Bin 0 -> 3056 bytes system/images/media/thumb-cad.png | Bin 0 -> 3021 bytes system/images/media/thumb-cdr.png | Bin 0 -> 2713 bytes system/images/media/thumb-cer.png | Bin 0 -> 2367 bytes system/images/media/thumb-cfg.png | Bin 0 -> 2764 bytes system/images/media/thumb-cfm.png | Bin 0 -> 2589 bytes system/images/media/thumb-cgi.png | Bin 0 -> 2626 bytes system/images/media/thumb-com.png | Bin 0 -> 3292 bytes system/images/media/thumb-cpl.png | Bin 0 -> 2174 bytes system/images/media/thumb-cpp.png | Bin 0 -> 2425 bytes system/images/media/thumb-crx.png | Bin 0 -> 2965 bytes system/images/media/thumb-csr.png | Bin 0 -> 3158 bytes system/images/media/thumb-css.png | Bin 0 -> 3235 bytes system/images/media/thumb-csv.png | Bin 0 -> 3317 bytes system/images/media/thumb-cue.png | Bin 0 -> 2271 bytes system/images/media/thumb-cur.png | Bin 0 -> 2716 bytes system/images/media/thumb-dat.png | Bin 0 -> 2188 bytes system/images/media/thumb-db.png | Bin 0 -> 1964 bytes system/images/media/thumb-dbf.png | Bin 0 -> 1947 bytes system/images/media/thumb-dds.png | Bin 0 -> 2815 bytes system/images/media/thumb-dem.png | Bin 0 -> 2215 bytes system/images/media/thumb-dll.png | Bin 0 -> 1352 bytes system/images/media/thumb-dmg.png | Bin 0 -> 3064 bytes system/images/media/thumb-dmp.png | Bin 0 -> 2527 bytes system/images/media/thumb-doc.png | Bin 0 -> 3051 bytes system/images/media/thumb-docx.png | Bin 0 -> 3865 bytes system/images/media/thumb-drv.png | Bin 0 -> 2728 bytes system/images/media/thumb-dtd.png | Bin 0 -> 1949 bytes system/images/media/thumb-dwg.png | Bin 0 -> 3530 bytes system/images/media/thumb-dxf.png | Bin 0 -> 2392 bytes system/images/media/thumb-elf.png | Bin 0 -> 663 bytes system/images/media/thumb-eot.png | Bin 0 -> 2002 bytes system/images/media/thumb-eps.png | Bin 0 -> 2276 bytes system/images/media/thumb-exe.png | Bin 0 -> 1813 bytes system/images/media/thumb-fla.png | Bin 0 -> 1724 bytes system/images/media/thumb-flv.png | Bin 0 -> 1720 bytes system/images/media/thumb-fnt.png | Bin 0 -> 1254 bytes system/images/media/thumb-fon.png | Bin 0 -> 2402 bytes system/images/media/thumb-gam.png | Bin 0 -> 3203 bytes system/images/media/thumb-gbr.png | Bin 0 -> 2849 bytes system/images/media/thumb-ged.png | Bin 0 -> 2269 bytes system/images/media/thumb-gif.png | Bin 0 -> 1751 bytes system/images/media/thumb-gpx.png | Bin 0 -> 2972 bytes system/images/media/thumb-gz.png | Bin 0 -> 2134 bytes system/images/media/thumb-gzip.png | Bin 0 -> 2451 bytes system/images/media/thumb-hqz.png | Bin 0 -> 2604 bytes system/images/media/thumb-html.png | Bin 0 -> 1740 bytes system/images/media/thumb-icns.png | Bin 0 -> 3212 bytes system/images/media/thumb-ico.png | Bin 0 -> 2769 bytes system/images/media/thumb-ics.png | Bin 0 -> 2787 bytes system/images/media/thumb-iff.png | Bin 0 -> 601 bytes system/images/media/thumb-indd.png | Bin 0 -> 2475 bytes system/images/media/thumb-iso.png | Bin 0 -> 2864 bytes system/images/media/thumb-jar.png | Bin 0 -> 2583 bytes system/images/media/thumb-jpg.png | Bin 0 -> 2435 bytes system/images/media/thumb-js.png | Bin 0 -> 2046 bytes system/images/media/thumb-json.png | Bin 0 -> 7818 bytes system/images/media/thumb-jsp.png | Bin 0 -> 2498 bytes system/images/media/thumb-key.png | Bin 0 -> 2130 bytes system/images/media/thumb-kml.png | Bin 0 -> 2346 bytes system/images/media/thumb-kmz.png | Bin 0 -> 2701 bytes system/images/media/thumb-lnk.png | Bin 0 -> 1971 bytes system/images/media/thumb-log.png | Bin 0 -> 2762 bytes system/images/media/thumb-lua.png | Bin 0 -> 2117 bytes system/images/media/thumb-m3u.png | Bin 0 -> 2909 bytes system/images/media/thumb-m4a.png | Bin 0 -> 2754 bytes system/images/media/thumb-m4v.png | Bin 0 -> 2738 bytes system/images/media/thumb-max.png | Bin 0 -> 3213 bytes system/images/media/thumb-mdb.png | Bin 0 -> 2691 bytes system/images/media/thumb-mdf.png | Bin 0 -> 2243 bytes system/images/media/thumb-mid.png | Bin 0 -> 2199 bytes system/images/media/thumb-mim.png | Bin 0 -> 2284 bytes system/images/media/thumb-mov.png | Bin 0 -> 3221 bytes system/images/media/thumb-mp3.png | Bin 0 -> 2801 bytes system/images/media/thumb-mp4.png | Bin 0 -> 2221 bytes system/images/media/thumb-mpa.png | Bin 0 -> 2734 bytes system/images/media/thumb-mpe.png | Bin 0 -> 1971 bytes system/images/media/thumb-mpg.png | Bin 0 -> 2811 bytes system/images/media/thumb-msg.png | Bin 0 -> 3319 bytes system/images/media/thumb-msi.png | Bin 0 -> 2594 bytes system/images/media/thumb-nes.png | Bin 0 -> 2320 bytes system/images/media/thumb-obj.png | Bin 0 -> 2716 bytes system/images/media/thumb-odb.png | Bin 0 -> 2912 bytes system/images/media/thumb-odc.png | Bin 0 -> 3239 bytes system/images/media/thumb-odf.png | Bin 0 -> 2496 bytes system/images/media/thumb-odg.png | Bin 0 -> 3069 bytes system/images/media/thumb-odi.png | Bin 0 -> 2453 bytes system/images/media/thumb-odp.png | Bin 0 -> 2871 bytes system/images/media/thumb-ods.png | Bin 0 -> 3257 bytes system/images/media/thumb-odt.png | Bin 0 -> 2414 bytes system/images/media/thumb-odx.png | Bin 0 -> 3133 bytes system/images/media/thumb-ogg.png | Bin 0 -> 3577 bytes system/images/media/thumb-pct.png | Bin 0 -> 2248 bytes system/images/media/thumb-pdb.png | Bin 0 -> 2354 bytes system/images/media/thumb-pdf.png | Bin 0 -> 1823 bytes system/images/media/thumb-pif.png | Bin 0 -> 1103 bytes system/images/media/thumb-pkg.png | Bin 0 -> 2660 bytes system/images/media/thumb-pl.png | Bin 0 -> 1066 bytes system/images/media/thumb-png.png | Bin 0 -> 2530 bytes system/images/media/thumb-pps.png | Bin 0 -> 2497 bytes system/images/media/thumb-ppt.png | Bin 0 -> 1573 bytes system/images/media/thumb-pptx.png | Bin 0 -> 2560 bytes system/images/media/thumb-ps.png | Bin 0 -> 2285 bytes system/images/media/thumb-psd.png | Bin 0 -> 2613 bytes system/images/media/thumb-pub.png | Bin 0 -> 2137 bytes system/images/media/thumb-py.png | Bin 0 -> 1776 bytes system/images/media/thumb-ra.png | Bin 0 -> 2223 bytes system/images/media/thumb-rar.png | Bin 0 -> 2695 bytes system/images/media/thumb-raw.png | Bin 0 -> 3433 bytes system/images/media/thumb-rm.png | Bin 0 -> 2181 bytes system/images/media/thumb-rom.png | Bin 0 -> 3088 bytes system/images/media/thumb-rpm.png | Bin 0 -> 2566 bytes system/images/media/thumb-rss.png | Bin 0 -> 3091 bytes system/images/media/thumb-rtf.png | Bin 0 -> 1364 bytes system/images/media/thumb-sav.png | Bin 0 -> 3204 bytes system/images/media/thumb-sdf.png | Bin 0 -> 2444 bytes system/images/media/thumb-sql.png | Bin 0 -> 2929 bytes system/images/media/thumb-srt.png | Bin 0 -> 2366 bytes system/images/media/thumb-svg.png | Bin 0 -> 3359 bytes system/images/media/thumb-swf.png | Bin 0 -> 3181 bytes system/images/media/thumb-sys.png | Bin 0 -> 3128 bytes system/images/media/thumb-tar.png | Bin 0 -> 2165 bytes system/images/media/thumb-tex.png | Bin 0 -> 1716 bytes system/images/media/thumb-tga.png | Bin 0 -> 2655 bytes system/images/media/thumb-thm.png | Bin 0 -> 1666 bytes system/images/media/thumb-tiff.png | Bin 0 -> 690 bytes system/images/media/thumb-tmp.png | Bin 0 -> 2040 bytes system/images/media/thumb-ttf.png | Bin 0 -> 615 bytes system/images/media/thumb-txt.png | Bin 0 -> 1853 bytes system/images/media/thumb-uue.png | Bin 0 -> 1632 bytes system/images/media/thumb-vb.png | Bin 0 -> 2074 bytes system/images/media/thumb-vcd.png | Bin 0 -> 3040 bytes system/images/media/thumb-vcf.png | Bin 0 -> 2581 bytes system/images/media/thumb-wav.png | Bin 0 -> 3601 bytes system/images/media/thumb-webm.png | Bin 0 -> 3843 bytes system/images/media/thumb-wma.png | Bin 0 -> 3552 bytes system/images/media/thumb-wmv.png | Bin 0 -> 3789 bytes system/images/media/thumb-woff.png | Bin 0 -> 3421 bytes system/images/media/thumb-woff2.png | Bin 0 -> 3927 bytes system/images/media/thumb-wpd.png | Bin 0 -> 3127 bytes system/images/media/thumb-wps.png | Bin 0 -> 3368 bytes system/images/media/thumb-wsf.png | Bin 0 -> 3028 bytes system/images/media/thumb-xls.png | Bin 0 -> 2562 bytes system/images/media/thumb-xlsx.png | Bin 0 -> 3481 bytes system/images/media/thumb-xml.png | Bin 0 -> 2557 bytes system/images/media/thumb-yuv.png | Bin 0 -> 2741 bytes system/images/media/thumb-zip.png | Bin 0 -> 1628 bytes system/images/media/thumb.png | Bin 0 -> 1200 bytes system/images/watermark.png | Bin 0 -> 95789 bytes system/install.php | 15 + system/languages/ar.yaml | 93 + system/languages/bg.yaml | 72 + system/languages/ca.yaml | 87 + system/languages/cs.yaml | 147 + system/languages/da.yaml | 90 + system/languages/de.yaml | 147 + system/languages/el.yaml | 144 + system/languages/en.yaml | 121 + system/languages/eo.yaml | 40 + system/languages/es.yaml | 107 + system/languages/et.yaml | 108 + system/languages/eu.yaml | 62 + system/languages/fa.yaml | 62 + system/languages/fi.yaml | 134 + system/languages/fr.yaml | 147 + system/languages/gl.yaml | 147 + system/languages/he.yaml | 99 + system/languages/hr.yaml | 104 + system/languages/hu.yaml | 97 + system/languages/id.yaml | 147 + system/languages/is.yaml | 80 + system/languages/it.yaml | 147 + system/languages/ja.yaml | 81 + system/languages/ko.yaml | 90 + system/languages/lt.yaml | 78 + system/languages/lv.yaml | 84 + system/languages/mn.yaml | 147 + system/languages/my.yaml | 147 + system/languages/nb.yaml | 4 + system/languages/nl.yaml | 147 + system/languages/no.yaml | 82 + system/languages/pl.yaml | 100 + system/languages/pt.yaml | 147 + system/languages/ro.yaml | 96 + system/languages/ru.yaml | 114 + system/languages/si.yaml | 120 + system/languages/sk.yaml | 144 + system/languages/sl.yaml | 85 + system/languages/sr.yaml | 147 + system/languages/sv.yaml | 100 + system/languages/sw.yaml | 147 + system/languages/th.yaml | 147 + system/languages/tr.yaml | 100 + system/languages/uk.yaml | 63 + system/languages/vi.yaml | 63 + system/languages/zh-cn.yaml | 146 + system/languages/zh-tw.yaml | 79 + system/languages/zh.yaml | 146 + system/pages/notfound.md | 6 + system/router.php | 55 + system/src/DOMLettersIterator.php | 165 + system/src/DOMWordsIterator.php | 158 + system/src/Grav/Common/Assets.php | 595 ++ system/src/Grav/Common/Assets/BaseAsset.php | 283 + system/src/Grav/Common/Assets/BlockAssets.php | 207 + system/src/Grav/Common/Assets/Css.php | 52 + system/src/Grav/Common/Assets/InlineCss.php | 44 + system/src/Grav/Common/Assets/InlineJs.php | 44 + .../src/Grav/Common/Assets/InlineJsModule.php | 46 + system/src/Grav/Common/Assets/Js.php | 48 + system/src/Grav/Common/Assets/JsModule.php | 49 + system/src/Grav/Common/Assets/Link.php | 43 + system/src/Grav/Common/Assets/Pipeline.php | 347 + .../Common/Assets/Traits/AssetUtilsTrait.php | 215 + .../Assets/Traits/LegacyAssetsTrait.php | 137 + .../Assets/Traits/TestingAssetsTrait.php | 350 + system/src/Grav/Common/Backup/Backups.php | 325 + system/src/Grav/Common/Browser.php | 153 + system/src/Grav/Common/Cache.php | 743 ++ system/src/Grav/Common/Composer.php | 67 + .../src/Grav/Common/Config/CompiledBase.php | 269 + .../Grav/Common/Config/CompiledBlueprints.php | 131 + .../src/Grav/Common/Config/CompiledConfig.php | 114 + .../Grav/Common/Config/CompiledLanguages.php | 83 + system/src/Grav/Common/Config/Config.php | 156 + .../Grav/Common/Config/ConfigFileFinder.php | 273 + system/src/Grav/Common/Config/Languages.php | 107 + system/src/Grav/Common/Config/Setup.php | 423 ++ system/src/Grav/Common/Data/Blueprint.php | 594 ++ .../src/Grav/Common/Data/BlueprintSchema.php | 461 ++ system/src/Grav/Common/Data/Blueprints.php | 121 + system/src/Grav/Common/Data/Data.php | 343 + system/src/Grav/Common/Data/DataInterface.php | 84 + system/src/Grav/Common/Data/Validation.php | 1238 ++++ .../Grav/Common/Data/ValidationException.php | 67 + system/src/Grav/Common/Debugger.php | 1148 +++ system/src/Grav/Common/Errors/BareHandler.php | 33 + system/src/Grav/Common/Errors/Errors.php | 85 + .../Grav/Common/Errors/Resources/error.css | 52 + .../Common/Errors/Resources/layout.html.php | 30 + .../Grav/Common/Errors/SimplePageHandler.php | 122 + .../src/Grav/Common/Errors/SystemFacade.php | 67 + system/src/Grav/Common/File/CompiledFile.php | 195 + .../src/Grav/Common/File/CompiledJsonFile.php | 33 + .../Grav/Common/File/CompiledMarkdownFile.php | 21 + .../src/Grav/Common/File/CompiledYamlFile.php | 21 + .../src/Grav/Common/Filesystem/Archiver.php | 108 + system/src/Grav/Common/Filesystem/Folder.php | 548 ++ .../RecursiveDirectoryFilterIterator.php | 119 + .../RecursiveFolderFilterIterator.php | 55 + .../Grav/Common/Filesystem/ZipArchiver.php | 166 + .../src/Grav/Common/Flex/FlexCollection.php | 28 + system/src/Grav/Common/Flex/FlexIndex.php | 29 + system/src/Grav/Common/Flex/FlexObject.php | 74 + .../Flex/Traits/FlexCollectionTrait.php | 51 + .../Common/Flex/Traits/FlexCommonTrait.php | 54 + .../Grav/Common/Flex/Traits/FlexGravTrait.php | 74 + .../Common/Flex/Traits/FlexIndexTrait.php | 20 + .../Common/Flex/Traits/FlexObjectTrait.php | 62 + .../Flex/Types/Generic/GenericCollection.php | 24 + .../Flex/Types/Generic/GenericIndex.php | 24 + .../Flex/Types/Generic/GenericObject.php | 22 + .../Flex/Types/Pages/PageCollection.php | 839 +++ .../Common/Flex/Types/Pages/PageIndex.php | 1198 +++ .../Common/Flex/Types/Pages/PageObject.php | 744 ++ .../Flex/Types/Pages/Storage/PageStorage.php | 700 ++ .../Types/Pages/Traits/PageContentTrait.php | 75 + .../Types/Pages/Traits/PageLegacyTrait.php | 236 + .../Types/Pages/Traits/PageRoutableTrait.php | 122 + .../Types/Pages/Traits/PageTranslateTrait.php | 108 + .../Types/UserGroups/UserGroupCollection.php | 56 + .../Flex/Types/UserGroups/UserGroupIndex.php | 24 + .../Flex/Types/UserGroups/UserGroupObject.php | 134 + .../Types/Users/Storage/UserFileStorage.php | 47 + .../Types/Users/Storage/UserFolderStorage.php | 37 + .../Users/Traits/UserObjectLegacyTrait.php | 94 + .../Flex/Types/Users/UserCollection.php | 135 + .../Common/Flex/Types/Users/UserIndex.php | 206 + .../Common/Flex/Types/Users/UserObject.php | 1059 +++ system/src/Grav/Common/Form/FormFlash.php | 107 + .../Grav/Common/GPM/AbstractCollection.php | 41 + .../GPM/Common/AbstractPackageCollection.php | 50 + .../Common/GPM/Common/CachedCollection.php | 43 + system/src/Grav/Common/GPM/Common/Package.php | 99 + system/src/Grav/Common/GPM/GPM.php | 1270 ++++ system/src/Grav/Common/GPM/Installer.php | 544 ++ system/src/Grav/Common/GPM/Licenses.php | 116 + .../GPM/Local/AbstractPackageCollection.php | 34 + system/src/Grav/Common/GPM/Local/Package.php | 51 + system/src/Grav/Common/GPM/Local/Packages.php | 29 + system/src/Grav/Common/GPM/Local/Plugins.php | 33 + system/src/Grav/Common/GPM/Local/Themes.php | 33 + .../GPM/Remote/AbstractPackageCollection.php | 81 + .../src/Grav/Common/GPM/Remote/GravCore.php | 151 + system/src/Grav/Common/GPM/Remote/Package.php | 66 + .../src/Grav/Common/GPM/Remote/Packages.php | 34 + system/src/Grav/Common/GPM/Remote/Plugins.php | 32 + system/src/Grav/Common/GPM/Remote/Themes.php | 32 + system/src/Grav/Common/GPM/Response.php | 3 + system/src/Grav/Common/GPM/Upgrader.php | 138 + system/src/Grav/Common/Getters.php | 170 + system/src/Grav/Common/Grav.php | 829 +++ system/src/Grav/Common/GravTrait.php | 34 + system/src/Grav/Common/HTTP/Client.php | 130 + system/src/Grav/Common/HTTP/Response.php | 96 + system/src/Grav/Common/Helpers/Base32.php | 141 + system/src/Grav/Common/Helpers/Excerpts.php | 196 + system/src/Grav/Common/Helpers/Exif.php | 48 + system/src/Grav/Common/Helpers/LogViewer.php | 167 + system/src/Grav/Common/Helpers/Truncator.php | 344 + system/src/Grav/Common/Helpers/YamlLinter.php | 122 + system/src/Grav/Common/Inflector.php | 363 + system/src/Grav/Common/Iterator.php | 264 + system/src/Grav/Common/Language/Language.php | 663 ++ .../Grav/Common/Language/LanguageCodes.php | 246 + system/src/Grav/Common/Markdown/Parsedown.php | 43 + .../Grav/Common/Markdown/ParsedownExtra.php | 46 + .../Common/Markdown/ParsedownGravTrait.php | 319 + .../Media/Interfaces/AudioMediaInterface.php | 25 + .../Interfaces/ImageManipulateInterface.php | 120 + .../Media/Interfaces/ImageMediaInterface.php | 17 + .../Interfaces/MediaCollectionInterface.php | 115 + .../Media/Interfaces/MediaFileInterface.php | 53 + .../Media/Interfaces/MediaInterface.php | 17 + .../Media/Interfaces/MediaLinkInterface.php | 17 + .../Media/Interfaces/MediaObjectInterface.php | 227 + .../Media/Interfaces/MediaPlayerInterface.php | 56 + .../Media/Interfaces/MediaUploadInterface.php | 73 + .../Media/Interfaces/VideoMediaInterface.php | 32 + .../Common/Media/Traits/AudioMediaTrait.php | 53 + .../Media/Traits/ImageDecodingTrait.php | 40 + .../Media/Traits/ImageFetchPriorityTrait.php | 40 + .../Common/Media/Traits/ImageLoadingTrait.php | 37 + .../Common/Media/Traits/ImageMediaTrait.php | 428 ++ .../Common/Media/Traits/MediaFileTrait.php | 139 + .../Common/Media/Traits/MediaObjectTrait.php | 630 ++ .../Common/Media/Traits/MediaPlayerTrait.php | 113 + .../Grav/Common/Media/Traits/MediaTrait.php | 153 + .../Common/Media/Traits/MediaUploadTrait.php | 680 ++ .../Common/Media/Traits/StaticResizeTrait.php | 40 + .../Media/Traits/ThumbnailMediaTrait.php | 149 + .../Common/Media/Traits/VideoMediaTrait.php | 68 + system/src/Grav/Common/Page/Collection.php | 710 ++ system/src/Grav/Common/Page/Header.php | 38 + .../Interfaces/PageCollectionInterface.php | 310 + .../Page/Interfaces/PageContentInterface.php | 267 + .../Page/Interfaces/PageFormInterface.php | 33 + .../Common/Page/Interfaces/PageInterface.php | 25 + .../Page/Interfaces/PageLegacyInterface.php | 475 ++ .../Page/Interfaces/PageRoutableInterface.php | 180 + .../Interfaces/PageTranslateInterface.php | 38 + .../Page/Interfaces/PagesSourceInterface.php | 56 + .../Grav/Common/Page/Markdown/Excerpts.php | 343 + system/src/Grav/Common/Page/Media.php | 286 + .../Grav/Common/Page/Medium/AbstractMedia.php | 344 + .../Grav/Common/Page/Medium/AudioMedium.php | 36 + .../Grav/Common/Page/Medium/GlobalMedia.php | 150 + .../src/Grav/Common/Page/Medium/ImageFile.php | 235 + .../Grav/Common/Page/Medium/ImageMedium.php | 499 ++ system/src/Grav/Common/Page/Medium/Link.php | 102 + system/src/Grav/Common/Page/Medium/Medium.php | 140 + .../Grav/Common/Page/Medium/MediumFactory.php | 220 + .../Common/Page/Medium/ParsedownHtmlTrait.php | 44 + .../Page/Medium/RenderableInterface.php | 41 + .../Common/Page/Medium/StaticImageMedium.php | 48 + .../Common/Page/Medium/StaticResizeTrait.php | 24 + .../Page/Medium/ThumbnailImageMedium.php | 21 + .../Common/Page/Medium/VectorImageMedium.php | 68 + .../Grav/Common/Page/Medium/VideoMedium.php | 36 + system/src/Grav/Common/Page/Page.php | 2935 ++++++++ system/src/Grav/Common/Page/Pages.php | 2258 ++++++ .../Grav/Common/Page/Traits/PageFormTrait.php | 126 + system/src/Grav/Common/Page/Types.php | 179 + system/src/Grav/Common/Plugin.php | 472 ++ system/src/Grav/Common/Plugins.php | 330 + .../Common/Processors/AssetsProcessor.php | 41 + .../Common/Processors/BackupsProcessor.php | 41 + .../Processors/DebuggerAssetsProcessor.php | 40 + .../Processors/Events/RequestHandlerEvent.php | 82 + .../Common/Processors/InitializeProcessor.php | 461 ++ .../Grav/Common/Processors/PagesProcessor.php | 115 + .../Common/Processors/PluginsProcessor.php | 41 + .../Grav/Common/Processors/ProcessorBase.php | 70 + .../Common/Processors/ProcessorInterface.php | 20 + .../Common/Processors/RenderProcessor.php | 71 + .../Common/Processors/RequestProcessor.php | 66 + .../Common/Processors/SchedulerProcessor.php | 42 + .../Grav/Common/Processors/TasksProcessor.php | 71 + .../Common/Processors/ThemesProcessor.php | 40 + .../Grav/Common/Processors/TwigProcessor.php | 40 + system/src/Grav/Common/Scheduler/Cron.php | 577 ++ .../Grav/Common/Scheduler/IntervalTrait.php | 404 + system/src/Grav/Common/Scheduler/Job.php | 1073 +++ .../src/Grav/Common/Scheduler/JobHistory.php | 462 ++ system/src/Grav/Common/Scheduler/JobQueue.php | 588 ++ .../src/Grav/Common/Scheduler/Scheduler.php | 1108 +++ .../Common/Scheduler/SchedulerController.php | 270 + system/src/Grav/Common/Security.php | 287 + .../Service/AccountsServiceProvider.php | 157 + .../Common/Service/AssetsServiceProvider.php | 32 + .../Common/Service/BackupsServiceProvider.php | 35 + .../Common/Service/ConfigServiceProvider.php | 206 + .../Common/Service/ErrorServiceProvider.php | 30 + .../Service/FilesystemServiceProvider.php | 32 + .../Common/Service/FlexServiceProvider.php | 121 + .../Service/InflectorServiceProvider.php | 32 + .../Common/Service/LoggerServiceProvider.php | 42 + .../Common/Service/OutputServiceProvider.php | 39 + .../Common/Service/PagesServiceProvider.php | 140 + .../Common/Service/RequestServiceProvider.php | 103 + .../Service/SchedulerServiceProvider.php | 64 + .../Common/Service/SessionServiceProvider.php | 134 + .../Common/Service/StreamsServiceProvider.php | 56 + .../Common/Service/TaskServiceProvider.php | 55 + system/src/Grav/Common/Session.php | 202 + system/src/Grav/Common/Taxonomy.php | 181 + system/src/Grav/Common/Theme.php | 87 + system/src/Grav/Common/Themes.php | 417 ++ .../Common/Twig/Exception/TwigException.php | 21 + .../Twig/Extension/FilesystemExtension.php | 387 + .../Common/Twig/Extension/GravExtension.php | 1756 +++++ .../Grav/Common/Twig/Node/TwigNodeCache.php | 93 + .../Grav/Common/Twig/Node/TwigNodeLink.php | 114 + .../Common/Twig/Node/TwigNodeMarkdown.php | 52 + .../Grav/Common/Twig/Node/TwigNodeRender.php | 84 + .../Grav/Common/Twig/Node/TwigNodeScript.php | 142 + .../Grav/Common/Twig/Node/TwigNodeStyle.php | 133 + .../Grav/Common/Twig/Node/TwigNodeSwitch.php | 88 + .../Grav/Common/Twig/Node/TwigNodeThrow.php | 52 + .../Common/Twig/Node/TwigNodeTryCatch.php | 67 + .../Twig/TokenParser/TwigTokenParserCache.php | 74 + .../Twig/TokenParser/TwigTokenParserLink.php | 109 + .../TokenParser/TwigTokenParserMarkdown.php | 59 + .../TokenParser/TwigTokenParserRender.php | 74 + .../TokenParser/TwigTokenParserScript.php | 132 + .../Twig/TokenParser/TwigTokenParserStyle.php | 119 + .../TokenParser/TwigTokenParserSwitch.php | 132 + .../Twig/TokenParser/TwigTokenParserThrow.php | 55 + .../TokenParser/TwigTokenParserTryCatch.php | 81 + system/src/Grav/Common/Twig/Twig.php | 578 ++ .../Common/Twig/TwigClockworkDataSource.php | 58 + .../Grav/Common/Twig/TwigClockworkDumper.php | 72 + .../src/Grav/Common/Twig/TwigEnvironment.php | 60 + system/src/Grav/Common/Twig/TwigExtension.php | 21 + .../Grav/Common/Twig/WriteCacheFileTrait.php | 56 + system/src/Grav/Common/Uri.php | 1527 ++++ system/src/Grav/Common/User/Access.php | 52 + .../src/Grav/Common/User/Authentication.php | 61 + system/src/Grav/Common/User/DataUser/User.php | 329 + .../Common/User/DataUser/UserCollection.php | 163 + system/src/Grav/Common/User/Group.php | 172 + .../User/Interfaces/AuthorizeInterface.php | 26 + .../Interfaces/UserCollectionInterface.php | 40 + .../User/Interfaces/UserGroupInterface.php | 18 + .../Common/User/Interfaces/UserInterface.php | 189 + .../src/Grav/Common/User/Traits/UserTrait.php | 233 + system/src/Grav/Common/User/User.php | 144 + system/src/Grav/Common/Utils.php | 2227 ++++++ system/src/Grav/Common/Yaml.php | 65 + .../Grav/Console/Application/Application.php | 138 + .../CommandLoader/PluginCommandLoader.php | 103 + .../Console/Application/GpmApplication.php | 42 + .../Console/Application/GravApplication.php | 52 + .../Console/Application/PluginApplication.php | 116 + system/src/Grav/Console/Cli/BackupCommand.php | 138 + system/src/Grav/Console/Cli/CleanCommand.php | 411 ++ .../Grav/Console/Cli/ClearCacheCommand.php | 104 + .../src/Grav/Console/Cli/ComposerCommand.php | 64 + .../src/Grav/Console/Cli/InstallCommand.php | 302 + .../src/Grav/Console/Cli/LogViewerCommand.php | 96 + .../Grav/Console/Cli/NewProjectCommand.php | 75 + .../Cli/PageSystemValidatorCommand.php | 299 + .../src/Grav/Console/Cli/SandboxCommand.php | 347 + .../src/Grav/Console/Cli/SchedulerCommand.php | 276 + .../src/Grav/Console/Cli/SecurityCommand.php | 102 + system/src/Grav/Console/Cli/ServerCommand.php | 154 + .../Grav/Console/Cli/YamlLinterCommand.php | 124 + system/src/Grav/Console/ConsoleCommand.php | 46 + system/src/Grav/Console/ConsoleTrait.php | 338 + .../Grav/Console/Gpm/DirectInstallCommand.php | 321 + system/src/Grav/Console/Gpm/IndexCommand.php | 335 + system/src/Grav/Console/Gpm/InfoCommand.php | 191 + .../src/Grav/Console/Gpm/InstallCommand.php | 726 ++ .../Grav/Console/Gpm/SelfupgradeCommand.php | 344 + .../src/Grav/Console/Gpm/UninstallCommand.php | 312 + system/src/Grav/Console/Gpm/UpdateCommand.php | 289 + .../src/Grav/Console/Gpm/VersionCommand.php | 125 + system/src/Grav/Console/GpmCommand.php | 68 + system/src/Grav/Console/GravCommand.php | 52 + .../Grav/Console/Plugin/PluginListCommand.php | 69 + .../Grav/Console/TerminalObjects/Table.php | 38 + .../Grav/Events/BeforeSessionStartEvent.php | 36 + system/src/Grav/Events/FlexRegisterEvent.php | 45 + system/src/Grav/Events/PageEvent.php | 18 + .../Grav/Events/PermissionsRegisterEvent.php | 45 + system/src/Grav/Events/PluginsLoadedEvent.php | 53 + system/src/Grav/Events/SessionStartEvent.php | 36 + system/src/Grav/Events/TypesEvent.php | 18 + system/src/Grav/Framework/Acl/Access.php | 231 + system/src/Grav/Framework/Acl/Action.php | 204 + system/src/Grav/Framework/Acl/Permissions.php | 249 + .../Grav/Framework/Acl/PermissionsReader.php | 186 + .../Framework/Acl/RecursiveActionIterator.php | 64 + .../Grav/Framework/Cache/AbstractCache.php | 32 + .../Framework/Cache/Adapter/ChainCache.php | 210 + .../Framework/Cache/Adapter/DoctrineCache.php | 118 + .../Framework/Cache/Adapter/FileCache.php | 266 + .../Framework/Cache/Adapter/MemoryCache.php | 83 + .../Framework/Cache/Adapter/SessionCache.php | 107 + .../Grav/Framework/Cache/CacheInterface.php | 71 + .../src/Grav/Framework/Cache/CacheTrait.php | 373 + .../Cache/Exception/CacheException.php | 21 + .../Exception/InvalidArgumentException.php | 20 + .../Collection/AbstractFileCollection.php | 238 + .../Collection/AbstractIndexCollection.php | 574 ++ .../Collection/AbstractLazyCollection.php | 97 + .../Framework/Collection/ArrayCollection.php | 117 + .../Collection/CollectionInterface.php | 69 + .../Framework/Collection/FileCollection.php | 97 + .../Collection/FileCollectionInterface.php | 33 + .../Grav/Framework/Compat/Serializable.php | 47 + .../Framework/ContentBlock/ContentBlock.php | 303 + .../ContentBlock/ContentBlockInterface.php | 90 + .../Grav/Framework/ContentBlock/HtmlBlock.php | 502 ++ .../ContentBlock/HtmlBlockInterface.php | 130 + .../Contracts/Media/MediaObjectInterface.php | 52 + .../Contracts/Object/IdentifierInterface.php | 27 + .../RelationshipIdentifierInterface.php | 28 + .../Relationships/RelationshipInterface.php | 81 + .../Relationships/RelationshipsInterface.php | 53 + .../ToManyRelationshipInterface.php | 55 + .../ToOneRelationshipInterface.php | 37 + .../Traits/ControllerResponseTrait.php | 307 + system/src/Grav/Framework/DI/Container.php | 35 + .../src/Grav/Framework/File/AbstractFile.php | 444 ++ system/src/Grav/Framework/File/CsvFile.php | 40 + system/src/Grav/Framework/File/DataFile.php | 78 + system/src/Grav/Framework/File/File.php | 35 + .../File/Formatter/AbstractFormatter.php | 117 + .../Framework/File/Formatter/CsvFormatter.php | 170 + .../File/Formatter/FormatterInterface.php | 12 + .../Framework/File/Formatter/IniFormatter.php | 68 + .../File/Formatter/JsonFormatter.php | 170 + .../File/Formatter/MarkdownFormatter.php | 161 + .../File/Formatter/SerializeFormatter.php | 98 + .../File/Formatter/YamlFormatter.php | 129 + system/src/Grav/Framework/File/IniFile.php | 40 + .../Interfaces/FileFormatterInterface.php | 72 + .../File/Interfaces/FileInterface.php | 180 + system/src/Grav/Framework/File/JsonFile.php | 31 + .../src/Grav/Framework/File/MarkdownFile.php | 40 + system/src/Grav/Framework/File/YamlFile.php | 40 + .../Grav/Framework/Filesystem/Filesystem.php | 356 + .../Interfaces/FilesystemInterface.php | 84 + system/src/Grav/Framework/Flex/Flex.php | 334 + .../Grav/Framework/Flex/FlexCollection.php | 733 ++ .../src/Grav/Framework/Flex/FlexDirectory.php | 1187 +++ .../Grav/Framework/Flex/FlexDirectoryForm.php | 509 ++ system/src/Grav/Framework/Flex/FlexForm.php | 610 ++ .../src/Grav/Framework/Flex/FlexFormFlash.php | 130 + .../Grav/Framework/Flex/FlexIdentifier.php | 75 + system/src/Grav/Framework/Flex/FlexIndex.php | 930 +++ system/src/Grav/Framework/Flex/FlexObject.php | 1288 ++++ .../Interfaces/FlexAuthorizeInterface.php | 33 + .../Interfaces/FlexCollectionInterface.php | 144 + .../Flex/Interfaces/FlexCommonInterface.php | 79 + .../Interfaces/FlexDirectoryFormInterface.php | 27 + .../Interfaces/FlexDirectoryInterface.php | 228 + .../Flex/Interfaces/FlexFormInterface.php | 51 + .../Flex/Interfaces/FlexIndexInterface.php | 64 + .../Flex/Interfaces/FlexInterface.php | 100 + .../Interfaces/FlexObjectFormInterface.php | 27 + .../Flex/Interfaces/FlexObjectInterface.php | 211 + .../Flex/Interfaces/FlexStorageInterface.php | 138 + .../Interfaces/FlexTranslateInterface.php | 51 + .../Flex/Pages/FlexPageCollection.php | 211 + .../Framework/Flex/Pages/FlexPageIndex.php | 48 + .../Framework/Flex/Pages/FlexPageObject.php | 496 ++ .../Flex/Pages/Traits/PageAuthorsTrait.php | 249 + .../Flex/Pages/Traits/PageContentTrait.php | 842 +++ .../Flex/Pages/Traits/PageLegacyTrait.php | 1124 +++ .../Flex/Pages/Traits/PageRoutableTrait.php | 550 ++ .../Flex/Pages/Traits/PageTranslateTrait.php | 291 + .../Storage/AbstractFilesystemStorage.php | 232 + .../Framework/Flex/Storage/FileStorage.php | 160 + .../Framework/Flex/Storage/FolderStorage.php | 708 ++ .../Framework/Flex/Storage/SimpleStorage.php | 507 ++ .../Flex/Traits/FlexAuthorizeTrait.php | 126 + .../Framework/Flex/Traits/FlexMediaTrait.php | 576 ++ .../Flex/Traits/FlexRelatedDirectoryTrait.php | 59 + .../Flex/Traits/FlexRelationshipsTrait.php | 61 + system/src/Grav/Framework/Form/FormFlash.php | 586 ++ .../src/Grav/Framework/Form/FormFlashFile.php | 266 + .../Form/Interfaces/FormFactoryInterface.php | 42 + .../Form/Interfaces/FormFlashInterface.php | 181 + .../Form/Interfaces/FormInterface.php | 187 + .../Grav/Framework/Form/Traits/FormTrait.php | 897 +++ .../Framework/Interfaces/RenderInterface.php | 38 + .../Logger/Processors/UserProcessor.php | 34 + .../Interfaces/MediaCollectionInterface.php | 23 + .../Media/Interfaces/MediaInterface.php | 37 + .../Interfaces/MediaManipulationInterface.php | 33 + .../Media/Interfaces/MediaObjectInterface.php | 47 + .../Grav/Framework/Media/MediaIdentifier.php | 150 + .../src/Grav/Framework/Media/MediaObject.php | 215 + .../Framework/Media/UploadedMediaObject.php | 172 + system/src/Grav/Framework/Mime/MimeTypes.php | 107 + .../Object/Access/ArrayAccessTrait.php | 66 + .../Object/Access/NestedArrayAccessTrait.php | 66 + .../Access/NestedPropertyCollectionTrait.php | 120 + .../Object/Access/NestedPropertyTrait.php | 180 + .../Object/Access/OverloadedPropertyTrait.php | 66 + .../src/Grav/Framework/Object/ArrayObject.php | 31 + .../Object/Base/ObjectCollectionTrait.php | 377 + .../Framework/Object/Base/ObjectTrait.php | 202 + .../Collection/ObjectExpressionVisitor.php | 240 + .../Object/Identifiers/Identifier.php | 66 + .../NestedObjectCollectionInterface.php | 64 + .../Interfaces/NestedObjectInterface.php | 60 + .../Interfaces/ObjectCollectionInterface.php | 126 + .../Object/Interfaces/ObjectInterface.php | 63 + .../src/Grav/Framework/Object/LazyObject.php | 34 + .../Framework/Object/ObjectCollection.php | 131 + .../src/Grav/Framework/Object/ObjectIndex.php | 281 + .../Object/Property/ArrayPropertyTrait.php | 115 + .../Object/Property/LazyPropertyTrait.php | 114 + .../Object/Property/MixedPropertyTrait.php | 121 + .../Object/Property/ObjectPropertyTrait.php | 213 + .../Grav/Framework/Object/PropertyObject.php | 32 + .../Pagination/AbstractPagination.php | 429 ++ .../Pagination/AbstractPaginationPage.php | 78 + .../Interfaces/PaginationInterface.php | 104 + .../Interfaces/PaginationPageInterface.php | 47 + .../Grav/Framework/Pagination/Pagination.php | 32 + .../Framework/Pagination/PaginationPage.php | 26 + .../src/Grav/Framework/Psr7/AbstractUri.php | 412 ++ system/src/Grav/Framework/Psr7/Request.php | 34 + system/src/Grav/Framework/Psr7/Response.php | 265 + .../src/Grav/Framework/Psr7/ServerRequest.php | 364 + system/src/Grav/Framework/Psr7/Stream.php | 43 + .../Psr7/Traits/MessageDecoratorTrait.php | 140 + .../Psr7/Traits/RequestDecoratorTrait.php | 112 + .../Psr7/Traits/ResponseDecoratorTrait.php | 82 + .../Traits/ServerRequestDecoratorTrait.php | 176 + .../Psr7/Traits/StreamDecoratorTrait.php | 153 + .../Traits/UploadedFileDecoratorTrait.php | 73 + .../Psr7/Traits/UriDecorationTrait.php | 188 + .../src/Grav/Framework/Psr7/UploadedFile.php | 70 + system/src/Grav/Framework/Psr7/Uri.php | 135 + .../Framework/Relationships/Relationships.php | 217 + .../Relationships/ToManyRelationship.php | 259 + .../Relationships/ToOneRelationship.php | 207 + .../Traits/RelationshipTrait.php | 128 + .../Exception/InvalidArgumentException.php | 49 + .../Exception/NotFoundException.php | 37 + .../Exception/NotHandledException.php | 20 + .../Exception/PageExpiredException.php | 32 + .../Exception/RequestException.php | 102 + .../RequestHandler/Middlewares/Exceptions.php | 78 + .../Middlewares/MultipartRequestSupport.php | 123 + .../RequestHandler/RequestHandler.php | 80 + .../Traits/RequestHandlerTrait.php | 64 + system/src/Grav/Framework/Route/Route.php | 452 ++ .../src/Grav/Framework/Route/RouteFactory.php | 236 + .../Session/Exceptions/SessionException.php | 20 + .../src/Grav/Framework/Session/Messages.php | 134 + system/src/Grav/Framework/Session/Session.php | 562 ++ .../Framework/Session/SessionInterface.php | 159 + system/src/Grav/Framework/Uri/Uri.php | 216 + system/src/Grav/Framework/Uri/UriFactory.php | 171 + .../src/Grav/Framework/Uri/UriPartsFilter.php | 145 + system/src/Grav/Installer/Install.php | 400 + .../src/Grav/Installer/InstallException.php | 29 + system/src/Grav/Installer/VersionUpdate.php | 83 + system/src/Grav/Installer/VersionUpdater.php | 133 + system/src/Grav/Installer/Versions.php | 329 + system/src/Grav/Installer/YamlUpdater.php | 431 ++ .../Installer/updates/1.7.0_2020-11-20_1.php | 24 + .../DeferredExtension/DeferredBlockNode.php | 43 + .../DeferredExtension/DeferredDeclareNode.php | 27 + .../DeferredExtension/DeferredExtension.php | 72 + .../DeferredInitializeNode.php | 27 + .../Twig/DeferredExtension/DeferredNode.php | 27 + .../DeferredExtension/DeferredNodeVisitor.php | 50 + .../DeferredNodeVisitorCompat.php | 67 + .../DeferredExtension/DeferredResolveNode.php | 27 + .../DeferredExtension/DeferredTokenParser.php | 77 + system/templates/default.html.twig | 4 + system/templates/external.html.twig | 1 + system/templates/flex/404.html.twig | 4 + .../flex/_default/collection/debug.html.twig | 5 + .../flex/_default/object/debug.html.twig | 4 + system/templates/modular/default.html.twig | 4 + system/templates/partials/messages.html.twig | 14 + system/templates/partials/metadata.html.twig | 3 + tests/_bootstrap.php | 35 + tests/_support/AcceptanceTester.php | 26 + tests/_support/FunctionalTester.php | 26 + tests/_support/Helper/Acceptance.php | 10 + tests/_support/Helper/Functional.php | 10 + tests/_support/Helper/Unit.php | 90 + tests/_support/UnitTester.php | 26 + tests/acceptance.suite.yml | 12 + tests/acceptance/_bootstrap.php | 2 + .../01.item1-1/01.item1-1-1/default.md | 10 + .../01.item1-1/02.item1-1-2/default.md | 10 + .../01.item1-1/03.item1-1-3/default.md | 10 + .../user/pages/01.item1/01.item1-1/default.md | 10 + .../02.item1-2/01.item1-2-1/default.md | 10 + .../02.item1-2/02.item1-2-2/default.md | 10 + .../02.item1-2/03.item1-2-3/default.md | 10 + .../user/pages/01.item1/02.item1-2/default.md | 10 + .../03.item1-3/01.item1-3-1/default.md | 10 + .../03.item1-3/02.item1-3-2/default.md | 10 + .../03.item1-3/03.item1-3-3/default.md | 10 + .../user/pages/01.item1/03.item1-3/default.md | 10 + .../user/pages/01.item1/default.md | 10 + .../user/pages/01.item1/existing-file.zip | Bin 0 -> 451 bytes .../user/pages/01.item1/home-cache-image.jpg | Bin 0 -> 156699 bytes .../user/pages/01.item1/home-sample-image.jpg | Bin 0 -> 156699 bytes .../01.item2-1/01.item2-1-1/default.md | 10 + .../01.item2-1/02.item2-1-2/default.md | 10 + .../01.item2-1/03.item2-1-3/default.md | 10 + .../user/pages/02.item2/01.item2-1/default.md | 10 + .../02.item2-2/01.item2-2-1/default.md | 10 + .../02.item2-2/02.item2-2-2/default.md | 10 + .../02.item2-2/03.item2-2-3/default.md | 10 + .../pages/02.item2/02.item2-2/cache-image.jpg | Bin 0 -> 156699 bytes .../user/pages/02.item2/02.item2-2/default.md | 10 + .../02.item2/02.item2-2/existing-file.zip | Bin 0 -> 451 bytes .../02.item2/02.item2-2/sample-image.jpg | Bin 0 -> 156699 bytes .../03.item2-3/01.item2-3-1/default.md | 10 + .../03.item2-3/02.item2-3-2/default.md | 10 + .../03.item2-3/03.item2-3-3/default.md | 10 + .../user/pages/02.item2/03.item2-3/default.md | 10 + .../user/pages/02.item2/default.md | 10 + .../01.item3-1/01.item3-1-1/default.md | 14 + .../01.item3-1/02.item3-1-2/default.md | 10 + .../01.item3-1/03.item3-1-3/default.md | 10 + .../user/pages/03.item3/01.item3-1/default.md | 10 + .../02.item3-2/01.item3-2-1/default.md | 10 + .../02.item3-2/02.item3-2-2/default.md | 10 + .../02.item3-2/03.item3-2-3/default.md | 10 + .../user/pages/03.item3/02.item3-2/default.md | 10 + .../03.item3-3/01.item3-3-1/default.md | 10 + .../03.item3-3/02.item3-3-2/default.md | 10 + .../03.item3-3/03.item3-3-3/default.md | 10 + .../user/pages/03.item3/03.item3-3/default.md | 10 + .../user/pages/03.item3/default.md | 10 + .../simple-site/user/pages/01.home/default.md | 10 + .../simple-site/user/pages/02.blog/blog.md | 10 + .../user/pages/02.blog/post-one/item.md | 10 + .../user/pages/02.blog/post-two/item.md | 10 + .../user/pages/03.about/default.md | 10 + .../pages/04.page-translated/default.en.md | 0 .../pages/04.page-translated/default.fr.md | 5 + .../05.translatedlong/part2/default.en.md | 0 .../05.translatedlong/part2/default.fr.md | 5 + .../user/pages/01.simple-page/default.en.md | 0 .../user/pages/01.simple-page/default.fr.md | 5 + .../single-pages/01.simple-page/default.md | 5 + tests/functional.suite.yml | 11 + .../Grav/Console/DirectInstallCommandTest.php | 38 + tests/functional/_bootstrap.php | 2 + .../UniformResourceLocatorExtension.php | 51 + tests/phpstan/extension.neon | 5 + tests/phpstan/phpstan-bootstrap.php | 7 + tests/phpstan/phpstan.neon | 175 + tests/phpstan/plugins-bootstrap.php | 64 + tests/phpstan/plugins.neon | 70 + tests/unit.suite.yml | 9 + tests/unit/Grav/Common/AssetsTest.php | 847 +++ tests/unit/Grav/Common/BrowserTest.php | 51 + tests/unit/Grav/Common/ComposerTest.php | 31 + tests/unit/Grav/Common/Data/BlueprintTest.php | 72 + tests/unit/Grav/Common/GPM/GPMTest.php | 329 + .../unit/Grav/Common/Helpers/ExcerptsTest.php | 120 + tests/unit/Grav/Common/InflectorTest.php | 147 + .../Common/Language/LanguageCodesTest.php | 27 + .../Grav/Common/Markdown/ParsedownTest.php | 1260 ++++ tests/unit/Grav/Common/Page/PagesTest.php | 299 + .../Twig/Extensions/GravExtensionTest.php | 202 + tests/unit/Grav/Common/UriTest.php | 1178 +++ tests/unit/Grav/Common/UtilsTest.php | 572 ++ .../Grav/Console/Gpm/InstallCommandTest.php | 28 + .../File/Formatter/CsvFormatterTest.php | 48 + .../Framework/Filesystem/FilesystemTest.php | 338 + tests/unit/_bootstrap.php | 3 + tests/unit/data/blueprints/strict.yaml | 15 + tmp/.gitkeep | 1 + user/accounts/.gitkeep | 1 + user/config/media.yaml | 0 user/config/site.yaml | 7 + user/config/system.yaml | 45 + user/config/themes/quark.yaml | 26 + user/data/.gitkeep | 1 + user/pages/01.home/bulle_texte.png | Bin 0 -> 22473 bytes user/pages/01.home/default.md | 15 + user/pages/01.home/oeil.gif | Bin 0 -> 58678 bytes user/pages/01.home/oeil_colere.png | Bin 0 -> 14689 bytes user/pages/01.home/robot_detruit.png | Bin 0 -> 70610 bytes user/pages/01.home/robot_guerrier.png | Bin 0 -> 78481 bytes user/pages/01.home/robot_neuf.png | Bin 0 -> 43987 bytes user/plugins/.gitkeep | 1 + user/themes/.gitkeep | 1 + user/themes/test/CHANGELOG.md | 221 + user/themes/test/LICENSE | 21 + user/themes/test/README.md | 153 + user/themes/test/assets/quark-screenshots.jpg | Bin 0 -> 198055 bytes user/themes/test/blueprints.yaml | 176 + user/themes/test/blueprints/blog.yaml | 94 + user/themes/test/blueprints/default.yaml | 15 + user/themes/test/blueprints/item.yaml | 113 + user/themes/test/blueprints/modular.yaml | 47 + .../test/blueprints/modular/features.yaml | 38 + user/themes/test/blueprints/modular/hero.yaml | 23 + user/themes/test/blueprints/modular/text.yaml | 19 + .../test/blueprints/partials/blog-bits.yaml | 64 + user/themes/test/css-compiled/spectre-exp.css | 369 + .../test/css-compiled/spectre-exp.min.css | 1 + .../test/css-compiled/spectre-icons.css | 172 + .../test/css-compiled/spectre-icons.min.css | 1 + user/themes/test/css-compiled/spectre.css | 1257 ++++ user/themes/test/css-compiled/spectre.min.css | 1 + user/themes/test/css-compiled/theme.css | 406 + user/themes/test/css-compiled/theme.min.css | 1 + user/themes/test/css/bricklayer.css | 49 + user/themes/test/css/custom.css | 27 + user/themes/test/css/line-awesome.min.css | 4 + user/themes/test/fonts/line-awesome.eot | Bin 0 -> 213245 bytes user/themes/test/fonts/line-awesome.svg | 2954 ++++++++ user/themes/test/fonts/line-awesome.ttf | Bin 0 -> 263504 bytes user/themes/test/fonts/line-awesome.woff | Bin 0 -> 117372 bytes user/themes/test/fonts/line-awesome.woff2 | Bin 0 -> 76372 bytes user/themes/test/gulpfile.js | 43 + user/themes/test/images/favicon.png | Bin 0 -> 13203 bytes user/themes/test/images/grav-logo.svg | 1 + user/themes/test/images/logo/.gitkeep | 0 user/themes/test/images/logo/oeil.gif | Bin 0 -> 53699 bytes user/themes/test/js/bricklayer.min.js | 1 + user/themes/test/js/jquery.treemenu.js | 87 + user/themes/test/js/perso.js | 361 + .../test/js/scopedQuerySelectorShim.min.js | 9 + user/themes/test/js/singlepagenav.min.js | 8 + user/themes/test/js/site.js | 59 + user/themes/test/js/smooth-scroll.min.js | 6 + user/themes/test/languages.yaml | 438 ++ user/themes/test/package.json | 49 + user/themes/test/screenshot.jpg | Bin 0 -> 159731 bytes user/themes/test/scss/spectre-exp.scss | 19 + user/themes/test/scss/spectre-icons.scss | 11 + user/themes/test/scss/spectre.scss | 53 + .../themes/test/scss/spectre/_accordions.scss | 38 + .../themes/test/scss/spectre/_animations.scss | 20 + user/themes/test/scss/spectre/_asian.scss | 43 + .../test/scss/spectre/_autocomplete.scss | 47 + user/themes/test/scss/spectre/_avatars.scss | 77 + user/themes/test/scss/spectre/_badges.scss | 60 + user/themes/test/scss/spectre/_bars.scss | 71 + user/themes/test/scss/spectre/_base.scss | 44 + .../test/scss/spectre/_breadcrumbs.scss | 29 + user/themes/test/scss/spectre/_buttons.scss | 193 + user/themes/test/scss/spectre/_calendars.scss | 222 + user/themes/test/scss/spectre/_cards.scss | 43 + user/themes/test/scss/spectre/_carousels.scss | 136 + user/themes/test/scss/spectre/_chips.scss | 33 + user/themes/test/scss/spectre/_codes.scss | 31 + .../scss/spectre/_comparison-sliders.scss | 115 + user/themes/test/scss/spectre/_dropdowns.scss | 36 + user/themes/test/scss/spectre/_empty.scss | 21 + user/themes/test/scss/spectre/_filters.scss | 37 + user/themes/test/scss/spectre/_forms.scss | 555 ++ user/themes/test/scss/spectre/_hero.scss | 22 + user/themes/test/scss/spectre/_icons.scss | 5 + user/themes/test/scss/spectre/_labels.scss | 34 + user/themes/test/scss/spectre/_layout.scss | 444 ++ user/themes/test/scss/spectre/_media.scss | 75 + user/themes/test/scss/spectre/_menus.scss | 66 + user/themes/test/scss/spectre/_meters.scss | 57 + user/themes/test/scss/spectre/_mixins.scss | 10 + user/themes/test/scss/spectre/_modals.scss | 87 + user/themes/test/scss/spectre/_navbar.scss | 28 + user/themes/test/scss/spectre/_navs.scss | 34 + user/themes/test/scss/spectre/_normalize.scss | 446 ++ .../themes/test/scss/spectre/_off-canvas.scss | 95 + .../themes/test/scss/spectre/_pagination.scss | 60 + user/themes/test/scss/spectre/_panels.scss | 23 + user/themes/test/scss/spectre/_parallax.scss | 135 + user/themes/test/scss/spectre/_popovers.scss | 65 + user/themes/test/scss/spectre/_progress.scss | 45 + user/themes/test/scss/spectre/_sliders.scss | 99 + user/themes/test/scss/spectre/_steps.scss | 71 + user/themes/test/scss/spectre/_tables.scss | 57 + user/themes/test/scss/spectre/_tabs.scss | 66 + user/themes/test/scss/spectre/_tiles.scss | 38 + user/themes/test/scss/spectre/_timelines.scss | 56 + user/themes/test/scss/spectre/_toasts.scss | 48 + user/themes/test/scss/spectre/_tooltips.scss | 79 + .../themes/test/scss/spectre/_typography.scss | 129 + user/themes/test/scss/spectre/_utilities.scss | 8 + user/themes/test/scss/spectre/_variables.scss | 117 + .../themes/test/scss/spectre/_viewer-360.scss | 34 + .../scss/spectre/icons/_icons-action.scss | 315 + .../test/scss/spectre/icons/_icons-core.scss | 54 + .../scss/spectre/icons/_icons-navigation.scss | 127 + .../scss/spectre/icons/_icons-object.scss | 161 + .../test/scss/spectre/mixins/_avatar.scss | 6 + .../test/scss/spectre/mixins/_button.scss | 54 + .../test/scss/spectre/mixins/_clearfix.scss | 8 + .../test/scss/spectre/mixins/_color.scss | 27 + .../test/scss/spectre/mixins/_label.scss | 11 + .../test/scss/spectre/mixins/_position.scss | 65 + .../test/scss/spectre/mixins/_shadow.scss | 9 + .../test/scss/spectre/mixins/_text.scss | 6 + .../test/scss/spectre/mixins/_toast.scss | 5 + .../themes/test/scss/spectre/spectre-exp.scss | 18 + .../test/scss/spectre/spectre-icons.scss | 10 + user/themes/test/scss/spectre/spectre.scss | 49 + .../test/scss/spectre/utilities/_colors.scss | 31 + .../test/scss/spectre/utilities/_cursors.scss | 24 + .../test/scss/spectre/utilities/_display.scss | 44 + .../test/scss/spectre/utilities/_divider.scss | 50 + .../test/scss/spectre/utilities/_loading.scss | 34 + .../scss/spectre/utilities/_position.scss | 54 + .../test/scss/spectre/utilities/_shapes.scss | 8 + .../test/scss/spectre/utilities/_text.scss | 64 + user/themes/test/scss/theme.scss | 21 + user/themes/test/scss/theme/_animation.scss | 23 + user/themes/test/scss/theme/_blog.scss | 114 + user/themes/test/scss/theme/_extensions.scss | 7 + user/themes/test/scss/theme/_fonts.scss | 1 + user/themes/test/scss/theme/_footer.scss | 17 + user/themes/test/scss/theme/_forms.scss | 77 + user/themes/test/scss/theme/_framework.scss | 156 + user/themes/test/scss/theme/_header.scss | 101 + user/themes/test/scss/theme/_menu.scss | 94 + user/themes/test/scss/theme/_mixins.scss | 77 + user/themes/test/scss/theme/_mobile.scss | 193 + user/themes/test/scss/theme/_onepage.scss | 122 + user/themes/test/scss/theme/_typography.scss | 178 + user/themes/test/scss/theme/_variables.scss | 38 + .../test/templates/blocks/base.html.twig | 3 + user/themes/test/templates/blog.html.twig | 63 + user/themes/test/templates/comments.html.twig | 7 + user/themes/test/templates/default.html.twig | 5 + user/themes/test/templates/error.html.twig | 12 + .../forms/fields/checkbox/checkbox.html.twig | 32 + .../fields/checkboxes/checkboxes.html.twig | 44 + .../forms/fields/radio/radio.html.twig | 26 + .../forms/fields/switch/switch.html.twig | 3 + user/themes/test/templates/item.html.twig | 41 + .../test/templates/macros/macros.html.twig | 16 + user/themes/test/templates/modular.html.twig | 60 + .../test/templates/modular/features.html.twig | 30 + .../test/templates/modular/gallery.html.twig | 83 + .../test/templates/modular/hero.html.twig | 4 + .../test/templates/modular/text.html.twig | 21 + .../templates/partials/archives.html.twig | 13 + .../test/templates/partials/base.html.twig | 112 + .../templates/partials/blog-item.html.twig | 30 + .../partials/blog-list-item.html.twig | 27 + .../templates/partials/blog/date.html.twig | 5 + .../partials/blog/page-summary.html.twig | 8 + .../partials/blog/taxonomy.html.twig | 7 + .../templates/partials/blog/title.html.twig | 11 + .../test/templates/partials/footer.html.twig | 5 + .../partials/form-messages.html.twig | 6 + .../test/templates/partials/hero.html.twig | 7 + .../test/templates/partials/layout.html.twig | 14 + .../test/templates/partials/logo.html.twig | 9 + .../templates/partials/messages.html.twig | 17 + .../templates/partials/navigation.html.twig | 6 + .../templates/partials/relatedpages.html.twig | 15 + .../test/templates/partials/sidebar.html.twig | 43 + .../templates/partials/taxonomylist.html.twig | 10 + user/themes/test/test.php | 56 + user/themes/test/test.yaml | 12 + user/themes/test/thumbnail.jpg | Bin 0 -> 49487 bytes user/themes/test/yarn.lock | 3680 +++++++++ webserver-configs/Caddyfile | 31 + webserver-configs/Caddyfile-0.8.x | 33 + webserver-configs/htaccess.txt | 78 + webserver-configs/lighttpd.conf | 48 + webserver-configs/nginx.conf | 44 + webserver-configs/web.config | 42 + 1082 files changed, 156212 insertions(+) create mode 100644 .dependencies create mode 100644 .editorconfig create mode 100644 .github/FUNDING.yml create mode 100644 .github/workflows/build.yaml create mode 100644 .github/workflows/tests.yaml create mode 100644 .github/workflows/trigger-skeletons.yml create mode 100644 .gitignore create mode 100644 .htaccess create mode 100644 .phan/config.php create mode 100644 .phan/internal_stubs/Redis.phan_php create mode 100644 .phan/internal_stubs/memcache.phan_php create mode 100644 .phan/internal_stubs/memcached.phan_php create mode 100644 .travis.yml create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 SECURITY.md create mode 100644 assets/.gitkeep create mode 100644 backup/.gitkeep create mode 100755 bin/composer.phar create mode 100755 bin/gpm create mode 100755 bin/grav create mode 100755 bin/plugin create mode 100644 cache/.gitkeep create mode 100644 codeception.yml create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 images/.gitkeep create mode 100644 index.php create mode 100644 logs/.gitkeep create mode 100644 now.json create mode 100644 robots.txt create mode 100644 system/assets/debugger/clockwork.css create mode 100644 system/assets/debugger/clockwork.js create mode 100644 system/assets/debugger/phpdebugbar.css create mode 100644 system/assets/grav.png create mode 100644 system/assets/jquery/jquery-2.1.4.min.js create mode 100644 system/assets/jquery/jquery-2.x.min.js create mode 100644 system/assets/jquery/jquery-3.x.min.js create mode 100644 system/assets/responsive-overlays/1x.png create mode 100644 system/assets/responsive-overlays/2x.png create mode 100644 system/assets/responsive-overlays/3x.png create mode 100644 system/assets/responsive-overlays/4x.png create mode 100644 system/assets/responsive-overlays/unknown.png create mode 100644 system/assets/whoops.css create mode 100644 system/blueprints/config/backups.yaml create mode 100644 system/blueprints/config/media.yaml create mode 100644 system/blueprints/config/scheduler.yaml create mode 100644 system/blueprints/config/security.yaml create mode 100644 system/blueprints/config/site.yaml create mode 100644 system/blueprints/config/streams.yaml create mode 100644 system/blueprints/config/system.yaml create mode 100644 system/blueprints/flex/accounts.yaml create mode 100644 system/blueprints/flex/configure/compat.yaml create mode 100644 system/blueprints/flex/pages.yaml create mode 100644 system/blueprints/flex/shared/configure.yaml create mode 100644 system/blueprints/flex/user-accounts.yaml create mode 100644 system/blueprints/flex/user-groups.yaml create mode 100644 system/blueprints/pages/default.yaml create mode 100644 system/blueprints/pages/external.yaml create mode 100644 system/blueprints/pages/modular.yaml create mode 100644 system/blueprints/pages/partials/security.yaml create mode 100644 system/blueprints/pages/root.yaml create mode 100644 system/blueprints/user/account.yaml create mode 100644 system/blueprints/user/account_new.yaml create mode 100644 system/blueprints/user/group.yaml create mode 100644 system/blueprints/user/group_new.yaml create mode 100644 system/config/backups.yaml create mode 100644 system/config/media.yaml create mode 100644 system/config/mime.yaml create mode 100644 system/config/permissions.yaml create mode 100644 system/config/scheduler.yaml create mode 100644 system/config/security.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/media/thumb-3dm.png create mode 100644 system/images/media/thumb-3ds.png create mode 100644 system/images/media/thumb-3g2.png create mode 100644 system/images/media/thumb-3gp.png create mode 100644 system/images/media/thumb-7z.png create mode 100644 system/images/media/thumb-aac.png create mode 100644 system/images/media/thumb-ai.png create mode 100644 system/images/media/thumb-aif.png create mode 100644 system/images/media/thumb-apk.png create mode 100644 system/images/media/thumb-app.png create mode 100644 system/images/media/thumb-asf.png create mode 100644 system/images/media/thumb-asp.png create mode 100644 system/images/media/thumb-aspx.png create mode 100644 system/images/media/thumb-asx.png create mode 100644 system/images/media/thumb-avi.png create mode 100644 system/images/media/thumb-bak.png create mode 100644 system/images/media/thumb-bat.png create mode 100644 system/images/media/thumb-bin.png create mode 100644 system/images/media/thumb-bmp.png create mode 100644 system/images/media/thumb-cab.png create mode 100644 system/images/media/thumb-cad.png create mode 100644 system/images/media/thumb-cdr.png create mode 100644 system/images/media/thumb-cer.png create mode 100644 system/images/media/thumb-cfg.png create mode 100644 system/images/media/thumb-cfm.png create mode 100644 system/images/media/thumb-cgi.png create mode 100644 system/images/media/thumb-com.png create mode 100644 system/images/media/thumb-cpl.png create mode 100644 system/images/media/thumb-cpp.png create mode 100644 system/images/media/thumb-crx.png create mode 100644 system/images/media/thumb-csr.png create mode 100644 system/images/media/thumb-css.png create mode 100644 system/images/media/thumb-csv.png create mode 100644 system/images/media/thumb-cue.png create mode 100644 system/images/media/thumb-cur.png create mode 100644 system/images/media/thumb-dat.png create mode 100644 system/images/media/thumb-db.png create mode 100644 system/images/media/thumb-dbf.png create mode 100644 system/images/media/thumb-dds.png create mode 100644 system/images/media/thumb-dem.png create mode 100644 system/images/media/thumb-dll.png create mode 100644 system/images/media/thumb-dmg.png create mode 100644 system/images/media/thumb-dmp.png create mode 100644 system/images/media/thumb-doc.png create mode 100644 system/images/media/thumb-docx.png create mode 100644 system/images/media/thumb-drv.png create mode 100644 system/images/media/thumb-dtd.png create mode 100644 system/images/media/thumb-dwg.png create mode 100644 system/images/media/thumb-dxf.png create mode 100644 system/images/media/thumb-elf.png create mode 100644 system/images/media/thumb-eot.png create mode 100644 system/images/media/thumb-eps.png create mode 100644 system/images/media/thumb-exe.png create mode 100644 system/images/media/thumb-fla.png create mode 100644 system/images/media/thumb-flv.png create mode 100644 system/images/media/thumb-fnt.png create mode 100644 system/images/media/thumb-fon.png create mode 100644 system/images/media/thumb-gam.png create mode 100644 system/images/media/thumb-gbr.png create mode 100644 system/images/media/thumb-ged.png create mode 100644 system/images/media/thumb-gif.png create mode 100644 system/images/media/thumb-gpx.png create mode 100644 system/images/media/thumb-gz.png create mode 100644 system/images/media/thumb-gzip.png create mode 100644 system/images/media/thumb-hqz.png create mode 100644 system/images/media/thumb-html.png create mode 100644 system/images/media/thumb-icns.png create mode 100644 system/images/media/thumb-ico.png create mode 100644 system/images/media/thumb-ics.png create mode 100644 system/images/media/thumb-iff.png create mode 100644 system/images/media/thumb-indd.png create mode 100644 system/images/media/thumb-iso.png create mode 100644 system/images/media/thumb-jar.png create mode 100644 system/images/media/thumb-jpg.png create mode 100644 system/images/media/thumb-js.png create mode 100644 system/images/media/thumb-json.png create mode 100644 system/images/media/thumb-jsp.png create mode 100644 system/images/media/thumb-key.png create mode 100644 system/images/media/thumb-kml.png create mode 100644 system/images/media/thumb-kmz.png create mode 100644 system/images/media/thumb-lnk.png create mode 100644 system/images/media/thumb-log.png create mode 100644 system/images/media/thumb-lua.png create mode 100644 system/images/media/thumb-m3u.png create mode 100644 system/images/media/thumb-m4a.png create mode 100644 system/images/media/thumb-m4v.png create mode 100644 system/images/media/thumb-max.png create mode 100644 system/images/media/thumb-mdb.png create mode 100644 system/images/media/thumb-mdf.png create mode 100644 system/images/media/thumb-mid.png create mode 100644 system/images/media/thumb-mim.png create mode 100644 system/images/media/thumb-mov.png create mode 100644 system/images/media/thumb-mp3.png create mode 100644 system/images/media/thumb-mp4.png create mode 100644 system/images/media/thumb-mpa.png create mode 100644 system/images/media/thumb-mpe.png create mode 100644 system/images/media/thumb-mpg.png create mode 100644 system/images/media/thumb-msg.png create mode 100644 system/images/media/thumb-msi.png create mode 100644 system/images/media/thumb-nes.png create mode 100644 system/images/media/thumb-obj.png create mode 100644 system/images/media/thumb-odb.png create mode 100644 system/images/media/thumb-odc.png create mode 100644 system/images/media/thumb-odf.png create mode 100644 system/images/media/thumb-odg.png create mode 100644 system/images/media/thumb-odi.png create mode 100644 system/images/media/thumb-odp.png create mode 100644 system/images/media/thumb-ods.png create mode 100644 system/images/media/thumb-odt.png create mode 100644 system/images/media/thumb-odx.png create mode 100644 system/images/media/thumb-ogg.png create mode 100644 system/images/media/thumb-pct.png create mode 100644 system/images/media/thumb-pdb.png create mode 100644 system/images/media/thumb-pdf.png create mode 100644 system/images/media/thumb-pif.png create mode 100644 system/images/media/thumb-pkg.png create mode 100644 system/images/media/thumb-pl.png create mode 100644 system/images/media/thumb-png.png create mode 100644 system/images/media/thumb-pps.png create mode 100644 system/images/media/thumb-ppt.png create mode 100644 system/images/media/thumb-pptx.png create mode 100644 system/images/media/thumb-ps.png create mode 100644 system/images/media/thumb-psd.png create mode 100644 system/images/media/thumb-pub.png create mode 100644 system/images/media/thumb-py.png create mode 100644 system/images/media/thumb-ra.png create mode 100644 system/images/media/thumb-rar.png create mode 100644 system/images/media/thumb-raw.png create mode 100644 system/images/media/thumb-rm.png create mode 100644 system/images/media/thumb-rom.png create mode 100644 system/images/media/thumb-rpm.png create mode 100644 system/images/media/thumb-rss.png create mode 100644 system/images/media/thumb-rtf.png create mode 100644 system/images/media/thumb-sav.png create mode 100644 system/images/media/thumb-sdf.png create mode 100644 system/images/media/thumb-sql.png create mode 100644 system/images/media/thumb-srt.png create mode 100644 system/images/media/thumb-svg.png create mode 100644 system/images/media/thumb-swf.png create mode 100644 system/images/media/thumb-sys.png create mode 100644 system/images/media/thumb-tar.png create mode 100644 system/images/media/thumb-tex.png create mode 100644 system/images/media/thumb-tga.png create mode 100644 system/images/media/thumb-thm.png create mode 100644 system/images/media/thumb-tiff.png create mode 100644 system/images/media/thumb-tmp.png create mode 100644 system/images/media/thumb-ttf.png create mode 100644 system/images/media/thumb-txt.png create mode 100644 system/images/media/thumb-uue.png create mode 100644 system/images/media/thumb-vb.png create mode 100644 system/images/media/thumb-vcd.png create mode 100644 system/images/media/thumb-vcf.png create mode 100644 system/images/media/thumb-wav.png create mode 100644 system/images/media/thumb-webm.png create mode 100644 system/images/media/thumb-wma.png create mode 100644 system/images/media/thumb-wmv.png create mode 100644 system/images/media/thumb-woff.png create mode 100644 system/images/media/thumb-woff2.png create mode 100644 system/images/media/thumb-wpd.png create mode 100644 system/images/media/thumb-wps.png create mode 100644 system/images/media/thumb-wsf.png create mode 100644 system/images/media/thumb-xls.png create mode 100644 system/images/media/thumb-xlsx.png create mode 100644 system/images/media/thumb-xml.png create mode 100644 system/images/media/thumb-yuv.png create mode 100644 system/images/media/thumb-zip.png create mode 100644 system/images/media/thumb.png create mode 100644 system/images/watermark.png create mode 100644 system/install.php create mode 100644 system/languages/ar.yaml create mode 100644 system/languages/bg.yaml create mode 100644 system/languages/ca.yaml create mode 100644 system/languages/cs.yaml create mode 100644 system/languages/da.yaml create mode 100644 system/languages/de.yaml create mode 100644 system/languages/el.yaml create mode 100644 system/languages/en.yaml create mode 100644 system/languages/eo.yaml create mode 100644 system/languages/es.yaml create mode 100644 system/languages/et.yaml create mode 100644 system/languages/eu.yaml create mode 100644 system/languages/fa.yaml create mode 100644 system/languages/fi.yaml create mode 100644 system/languages/fr.yaml create mode 100644 system/languages/gl.yaml create mode 100644 system/languages/he.yaml create mode 100644 system/languages/hr.yaml create mode 100644 system/languages/hu.yaml create mode 100644 system/languages/id.yaml create mode 100644 system/languages/is.yaml create mode 100644 system/languages/it.yaml create mode 100644 system/languages/ja.yaml create mode 100644 system/languages/ko.yaml create mode 100644 system/languages/lt.yaml create mode 100644 system/languages/lv.yaml create mode 100644 system/languages/mn.yaml create mode 100644 system/languages/my.yaml create mode 100644 system/languages/nb.yaml create mode 100644 system/languages/nl.yaml create mode 100644 system/languages/no.yaml create mode 100644 system/languages/pl.yaml create mode 100644 system/languages/pt.yaml create mode 100644 system/languages/ro.yaml create mode 100644 system/languages/ru.yaml create mode 100644 system/languages/si.yaml create mode 100644 system/languages/sk.yaml create mode 100644 system/languages/sl.yaml create mode 100644 system/languages/sr.yaml create mode 100644 system/languages/sv.yaml create mode 100644 system/languages/sw.yaml create mode 100644 system/languages/th.yaml create mode 100644 system/languages/tr.yaml create mode 100644 system/languages/uk.yaml create mode 100644 system/languages/vi.yaml create mode 100644 system/languages/zh-cn.yaml create mode 100644 system/languages/zh-tw.yaml create mode 100644 system/languages/zh.yaml create mode 100644 system/pages/notfound.md create mode 100644 system/router.php create mode 100644 system/src/DOMLettersIterator.php create mode 100644 system/src/DOMWordsIterator.php create mode 100644 system/src/Grav/Common/Assets.php create mode 100644 system/src/Grav/Common/Assets/BaseAsset.php create mode 100644 system/src/Grav/Common/Assets/BlockAssets.php create mode 100644 system/src/Grav/Common/Assets/Css.php create mode 100644 system/src/Grav/Common/Assets/InlineCss.php create mode 100644 system/src/Grav/Common/Assets/InlineJs.php create mode 100644 system/src/Grav/Common/Assets/InlineJsModule.php create mode 100644 system/src/Grav/Common/Assets/Js.php create mode 100644 system/src/Grav/Common/Assets/JsModule.php create mode 100644 system/src/Grav/Common/Assets/Link.php create mode 100644 system/src/Grav/Common/Assets/Pipeline.php create mode 100644 system/src/Grav/Common/Assets/Traits/AssetUtilsTrait.php create mode 100644 system/src/Grav/Common/Assets/Traits/LegacyAssetsTrait.php create mode 100644 system/src/Grav/Common/Assets/Traits/TestingAssetsTrait.php create mode 100644 system/src/Grav/Common/Backup/Backups.php create mode 100644 system/src/Grav/Common/Browser.php create mode 100644 system/src/Grav/Common/Cache.php create mode 100644 system/src/Grav/Common/Composer.php create mode 100644 system/src/Grav/Common/Config/CompiledBase.php create mode 100644 system/src/Grav/Common/Config/CompiledBlueprints.php create mode 100644 system/src/Grav/Common/Config/CompiledConfig.php create mode 100644 system/src/Grav/Common/Config/CompiledLanguages.php create mode 100644 system/src/Grav/Common/Config/Config.php create mode 100644 system/src/Grav/Common/Config/ConfigFileFinder.php create mode 100644 system/src/Grav/Common/Config/Languages.php create mode 100644 system/src/Grav/Common/Config/Setup.php create mode 100644 system/src/Grav/Common/Data/Blueprint.php create mode 100644 system/src/Grav/Common/Data/BlueprintSchema.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/Data/ValidationException.php create mode 100644 system/src/Grav/Common/Debugger.php create mode 100644 system/src/Grav/Common/Errors/BareHandler.php create mode 100644 system/src/Grav/Common/Errors/Errors.php create mode 100644 system/src/Grav/Common/Errors/Resources/error.css create mode 100644 system/src/Grav/Common/Errors/Resources/layout.html.php create mode 100644 system/src/Grav/Common/Errors/SimplePageHandler.php create mode 100644 system/src/Grav/Common/Errors/SystemFacade.php create mode 100644 system/src/Grav/Common/File/CompiledFile.php create mode 100644 system/src/Grav/Common/File/CompiledJsonFile.php create mode 100644 system/src/Grav/Common/File/CompiledMarkdownFile.php create mode 100644 system/src/Grav/Common/File/CompiledYamlFile.php create mode 100644 system/src/Grav/Common/Filesystem/Archiver.php create mode 100644 system/src/Grav/Common/Filesystem/Folder.php create mode 100644 system/src/Grav/Common/Filesystem/RecursiveDirectoryFilterIterator.php create mode 100644 system/src/Grav/Common/Filesystem/RecursiveFolderFilterIterator.php create mode 100644 system/src/Grav/Common/Filesystem/ZipArchiver.php create mode 100644 system/src/Grav/Common/Flex/FlexCollection.php create mode 100644 system/src/Grav/Common/Flex/FlexIndex.php create mode 100644 system/src/Grav/Common/Flex/FlexObject.php create mode 100644 system/src/Grav/Common/Flex/Traits/FlexCollectionTrait.php create mode 100644 system/src/Grav/Common/Flex/Traits/FlexCommonTrait.php create mode 100644 system/src/Grav/Common/Flex/Traits/FlexGravTrait.php create mode 100644 system/src/Grav/Common/Flex/Traits/FlexIndexTrait.php create mode 100644 system/src/Grav/Common/Flex/Traits/FlexObjectTrait.php create mode 100644 system/src/Grav/Common/Flex/Types/Generic/GenericCollection.php create mode 100644 system/src/Grav/Common/Flex/Types/Generic/GenericIndex.php create mode 100644 system/src/Grav/Common/Flex/Types/Generic/GenericObject.php create mode 100644 system/src/Grav/Common/Flex/Types/Pages/PageCollection.php create mode 100644 system/src/Grav/Common/Flex/Types/Pages/PageIndex.php create mode 100644 system/src/Grav/Common/Flex/Types/Pages/PageObject.php create mode 100644 system/src/Grav/Common/Flex/Types/Pages/Storage/PageStorage.php create mode 100644 system/src/Grav/Common/Flex/Types/Pages/Traits/PageContentTrait.php create mode 100644 system/src/Grav/Common/Flex/Types/Pages/Traits/PageLegacyTrait.php create mode 100644 system/src/Grav/Common/Flex/Types/Pages/Traits/PageRoutableTrait.php create mode 100644 system/src/Grav/Common/Flex/Types/Pages/Traits/PageTranslateTrait.php create mode 100644 system/src/Grav/Common/Flex/Types/UserGroups/UserGroupCollection.php create mode 100644 system/src/Grav/Common/Flex/Types/UserGroups/UserGroupIndex.php create mode 100644 system/src/Grav/Common/Flex/Types/UserGroups/UserGroupObject.php create mode 100644 system/src/Grav/Common/Flex/Types/Users/Storage/UserFileStorage.php create mode 100644 system/src/Grav/Common/Flex/Types/Users/Storage/UserFolderStorage.php create mode 100644 system/src/Grav/Common/Flex/Types/Users/Traits/UserObjectLegacyTrait.php create mode 100644 system/src/Grav/Common/Flex/Types/Users/UserCollection.php create mode 100644 system/src/Grav/Common/Flex/Types/Users/UserIndex.php create mode 100644 system/src/Grav/Common/Flex/Types/Users/UserObject.php create mode 100644 system/src/Grav/Common/Form/FormFlash.php create mode 100644 system/src/Grav/Common/GPM/AbstractCollection.php create mode 100644 system/src/Grav/Common/GPM/Common/AbstractPackageCollection.php create mode 100644 system/src/Grav/Common/GPM/Common/CachedCollection.php create mode 100644 system/src/Grav/Common/GPM/Common/Package.php create mode 100644 system/src/Grav/Common/GPM/GPM.php create mode 100644 system/src/Grav/Common/GPM/Installer.php create mode 100644 system/src/Grav/Common/GPM/Licenses.php create mode 100644 system/src/Grav/Common/GPM/Local/AbstractPackageCollection.php create mode 100644 system/src/Grav/Common/GPM/Local/Package.php create mode 100644 system/src/Grav/Common/GPM/Local/Packages.php create mode 100644 system/src/Grav/Common/GPM/Local/Plugins.php create mode 100644 system/src/Grav/Common/GPM/Local/Themes.php create mode 100644 system/src/Grav/Common/GPM/Remote/AbstractPackageCollection.php create mode 100644 system/src/Grav/Common/GPM/Remote/GravCore.php create mode 100644 system/src/Grav/Common/GPM/Remote/Package.php create mode 100644 system/src/Grav/Common/GPM/Remote/Packages.php create mode 100644 system/src/Grav/Common/GPM/Remote/Plugins.php create mode 100644 system/src/Grav/Common/GPM/Remote/Themes.php create mode 100644 system/src/Grav/Common/GPM/Response.php create mode 100644 system/src/Grav/Common/GPM/Upgrader.php create mode 100644 system/src/Grav/Common/Getters.php create mode 100644 system/src/Grav/Common/Grav.php create mode 100644 system/src/Grav/Common/GravTrait.php create mode 100644 system/src/Grav/Common/HTTP/Client.php create mode 100644 system/src/Grav/Common/HTTP/Response.php create mode 100644 system/src/Grav/Common/Helpers/Base32.php create mode 100644 system/src/Grav/Common/Helpers/Excerpts.php create mode 100644 system/src/Grav/Common/Helpers/Exif.php create mode 100644 system/src/Grav/Common/Helpers/LogViewer.php create mode 100644 system/src/Grav/Common/Helpers/Truncator.php create mode 100644 system/src/Grav/Common/Helpers/YamlLinter.php create mode 100644 system/src/Grav/Common/Inflector.php create mode 100644 system/src/Grav/Common/Iterator.php create mode 100644 system/src/Grav/Common/Language/Language.php create mode 100644 system/src/Grav/Common/Language/LanguageCodes.php create mode 100644 system/src/Grav/Common/Markdown/Parsedown.php create mode 100644 system/src/Grav/Common/Markdown/ParsedownExtra.php create mode 100644 system/src/Grav/Common/Markdown/ParsedownGravTrait.php create mode 100644 system/src/Grav/Common/Media/Interfaces/AudioMediaInterface.php create mode 100644 system/src/Grav/Common/Media/Interfaces/ImageManipulateInterface.php create mode 100644 system/src/Grav/Common/Media/Interfaces/ImageMediaInterface.php create mode 100644 system/src/Grav/Common/Media/Interfaces/MediaCollectionInterface.php create mode 100644 system/src/Grav/Common/Media/Interfaces/MediaFileInterface.php create mode 100644 system/src/Grav/Common/Media/Interfaces/MediaInterface.php create mode 100644 system/src/Grav/Common/Media/Interfaces/MediaLinkInterface.php create mode 100644 system/src/Grav/Common/Media/Interfaces/MediaObjectInterface.php create mode 100644 system/src/Grav/Common/Media/Interfaces/MediaPlayerInterface.php create mode 100644 system/src/Grav/Common/Media/Interfaces/MediaUploadInterface.php create mode 100644 system/src/Grav/Common/Media/Interfaces/VideoMediaInterface.php create mode 100644 system/src/Grav/Common/Media/Traits/AudioMediaTrait.php create mode 100644 system/src/Grav/Common/Media/Traits/ImageDecodingTrait.php create mode 100644 system/src/Grav/Common/Media/Traits/ImageFetchPriorityTrait.php create mode 100644 system/src/Grav/Common/Media/Traits/ImageLoadingTrait.php create mode 100644 system/src/Grav/Common/Media/Traits/ImageMediaTrait.php create mode 100644 system/src/Grav/Common/Media/Traits/MediaFileTrait.php create mode 100644 system/src/Grav/Common/Media/Traits/MediaObjectTrait.php create mode 100644 system/src/Grav/Common/Media/Traits/MediaPlayerTrait.php create mode 100644 system/src/Grav/Common/Media/Traits/MediaTrait.php create mode 100644 system/src/Grav/Common/Media/Traits/MediaUploadTrait.php create mode 100644 system/src/Grav/Common/Media/Traits/StaticResizeTrait.php create mode 100644 system/src/Grav/Common/Media/Traits/ThumbnailMediaTrait.php create mode 100644 system/src/Grav/Common/Media/Traits/VideoMediaTrait.php create mode 100644 system/src/Grav/Common/Page/Collection.php create mode 100644 system/src/Grav/Common/Page/Header.php create mode 100644 system/src/Grav/Common/Page/Interfaces/PageCollectionInterface.php create mode 100644 system/src/Grav/Common/Page/Interfaces/PageContentInterface.php create mode 100644 system/src/Grav/Common/Page/Interfaces/PageFormInterface.php create mode 100644 system/src/Grav/Common/Page/Interfaces/PageInterface.php create mode 100644 system/src/Grav/Common/Page/Interfaces/PageLegacyInterface.php create mode 100644 system/src/Grav/Common/Page/Interfaces/PageRoutableInterface.php create mode 100644 system/src/Grav/Common/Page/Interfaces/PageTranslateInterface.php create mode 100644 system/src/Grav/Common/Page/Interfaces/PagesSourceInterface.php create mode 100644 system/src/Grav/Common/Page/Markdown/Excerpts.php create mode 100644 system/src/Grav/Common/Page/Media.php create mode 100644 system/src/Grav/Common/Page/Medium/AbstractMedia.php create mode 100644 system/src/Grav/Common/Page/Medium/AudioMedium.php create mode 100644 system/src/Grav/Common/Page/Medium/GlobalMedia.php create mode 100644 system/src/Grav/Common/Page/Medium/ImageFile.php create mode 100644 system/src/Grav/Common/Page/Medium/ImageMedium.php create mode 100644 system/src/Grav/Common/Page/Medium/Link.php create mode 100644 system/src/Grav/Common/Page/Medium/Medium.php create mode 100644 system/src/Grav/Common/Page/Medium/MediumFactory.php create mode 100644 system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php create mode 100644 system/src/Grav/Common/Page/Medium/RenderableInterface.php create mode 100644 system/src/Grav/Common/Page/Medium/StaticImageMedium.php create mode 100644 system/src/Grav/Common/Page/Medium/StaticResizeTrait.php create mode 100644 system/src/Grav/Common/Page/Medium/ThumbnailImageMedium.php create mode 100644 system/src/Grav/Common/Page/Medium/VectorImageMedium.php create mode 100644 system/src/Grav/Common/Page/Medium/VideoMedium.php create mode 100644 system/src/Grav/Common/Page/Page.php create mode 100644 system/src/Grav/Common/Page/Pages.php create mode 100644 system/src/Grav/Common/Page/Traits/PageFormTrait.php create mode 100644 system/src/Grav/Common/Page/Types.php create mode 100644 system/src/Grav/Common/Plugin.php create mode 100644 system/src/Grav/Common/Plugins.php create mode 100644 system/src/Grav/Common/Processors/AssetsProcessor.php create mode 100644 system/src/Grav/Common/Processors/BackupsProcessor.php create mode 100644 system/src/Grav/Common/Processors/DebuggerAssetsProcessor.php create mode 100644 system/src/Grav/Common/Processors/Events/RequestHandlerEvent.php create mode 100644 system/src/Grav/Common/Processors/InitializeProcessor.php create mode 100644 system/src/Grav/Common/Processors/PagesProcessor.php create mode 100644 system/src/Grav/Common/Processors/PluginsProcessor.php create mode 100644 system/src/Grav/Common/Processors/ProcessorBase.php create mode 100644 system/src/Grav/Common/Processors/ProcessorInterface.php create mode 100644 system/src/Grav/Common/Processors/RenderProcessor.php create mode 100644 system/src/Grav/Common/Processors/RequestProcessor.php create mode 100644 system/src/Grav/Common/Processors/SchedulerProcessor.php create mode 100644 system/src/Grav/Common/Processors/TasksProcessor.php create mode 100644 system/src/Grav/Common/Processors/ThemesProcessor.php create mode 100644 system/src/Grav/Common/Processors/TwigProcessor.php create mode 100644 system/src/Grav/Common/Scheduler/Cron.php create mode 100644 system/src/Grav/Common/Scheduler/IntervalTrait.php create mode 100644 system/src/Grav/Common/Scheduler/Job.php create mode 100644 system/src/Grav/Common/Scheduler/JobHistory.php create mode 100644 system/src/Grav/Common/Scheduler/JobQueue.php create mode 100644 system/src/Grav/Common/Scheduler/Scheduler.php create mode 100644 system/src/Grav/Common/Scheduler/SchedulerController.php create mode 100644 system/src/Grav/Common/Security.php create mode 100644 system/src/Grav/Common/Service/AccountsServiceProvider.php create mode 100644 system/src/Grav/Common/Service/AssetsServiceProvider.php create mode 100644 system/src/Grav/Common/Service/BackupsServiceProvider.php create mode 100644 system/src/Grav/Common/Service/ConfigServiceProvider.php create mode 100644 system/src/Grav/Common/Service/ErrorServiceProvider.php create mode 100644 system/src/Grav/Common/Service/FilesystemServiceProvider.php create mode 100644 system/src/Grav/Common/Service/FlexServiceProvider.php create mode 100644 system/src/Grav/Common/Service/InflectorServiceProvider.php create mode 100644 system/src/Grav/Common/Service/LoggerServiceProvider.php create mode 100644 system/src/Grav/Common/Service/OutputServiceProvider.php create mode 100644 system/src/Grav/Common/Service/PagesServiceProvider.php create mode 100644 system/src/Grav/Common/Service/RequestServiceProvider.php create mode 100644 system/src/Grav/Common/Service/SchedulerServiceProvider.php create mode 100644 system/src/Grav/Common/Service/SessionServiceProvider.php create mode 100644 system/src/Grav/Common/Service/StreamsServiceProvider.php create mode 100644 system/src/Grav/Common/Service/TaskServiceProvider.php create mode 100644 system/src/Grav/Common/Session.php create mode 100644 system/src/Grav/Common/Taxonomy.php create mode 100644 system/src/Grav/Common/Theme.php create mode 100644 system/src/Grav/Common/Themes.php create mode 100644 system/src/Grav/Common/Twig/Exception/TwigException.php create mode 100644 system/src/Grav/Common/Twig/Extension/FilesystemExtension.php create mode 100644 system/src/Grav/Common/Twig/Extension/GravExtension.php create mode 100644 system/src/Grav/Common/Twig/Node/TwigNodeCache.php create mode 100644 system/src/Grav/Common/Twig/Node/TwigNodeLink.php create mode 100644 system/src/Grav/Common/Twig/Node/TwigNodeMarkdown.php create mode 100644 system/src/Grav/Common/Twig/Node/TwigNodeRender.php create mode 100644 system/src/Grav/Common/Twig/Node/TwigNodeScript.php create mode 100644 system/src/Grav/Common/Twig/Node/TwigNodeStyle.php create mode 100644 system/src/Grav/Common/Twig/Node/TwigNodeSwitch.php create mode 100644 system/src/Grav/Common/Twig/Node/TwigNodeThrow.php create mode 100644 system/src/Grav/Common/Twig/Node/TwigNodeTryCatch.php create mode 100644 system/src/Grav/Common/Twig/TokenParser/TwigTokenParserCache.php create mode 100644 system/src/Grav/Common/Twig/TokenParser/TwigTokenParserLink.php create mode 100644 system/src/Grav/Common/Twig/TokenParser/TwigTokenParserMarkdown.php create mode 100644 system/src/Grav/Common/Twig/TokenParser/TwigTokenParserRender.php create mode 100644 system/src/Grav/Common/Twig/TokenParser/TwigTokenParserScript.php create mode 100644 system/src/Grav/Common/Twig/TokenParser/TwigTokenParserStyle.php create mode 100644 system/src/Grav/Common/Twig/TokenParser/TwigTokenParserSwitch.php create mode 100644 system/src/Grav/Common/Twig/TokenParser/TwigTokenParserThrow.php create mode 100644 system/src/Grav/Common/Twig/TokenParser/TwigTokenParserTryCatch.php create mode 100644 system/src/Grav/Common/Twig/Twig.php create mode 100644 system/src/Grav/Common/Twig/TwigClockworkDataSource.php create mode 100644 system/src/Grav/Common/Twig/TwigClockworkDumper.php create mode 100644 system/src/Grav/Common/Twig/TwigEnvironment.php create mode 100644 system/src/Grav/Common/Twig/TwigExtension.php create mode 100644 system/src/Grav/Common/Twig/WriteCacheFileTrait.php create mode 100644 system/src/Grav/Common/Uri.php create mode 100644 system/src/Grav/Common/User/Access.php create mode 100644 system/src/Grav/Common/User/Authentication.php create mode 100644 system/src/Grav/Common/User/DataUser/User.php create mode 100644 system/src/Grav/Common/User/DataUser/UserCollection.php create mode 100644 system/src/Grav/Common/User/Group.php create mode 100644 system/src/Grav/Common/User/Interfaces/AuthorizeInterface.php create mode 100644 system/src/Grav/Common/User/Interfaces/UserCollectionInterface.php create mode 100644 system/src/Grav/Common/User/Interfaces/UserGroupInterface.php create mode 100644 system/src/Grav/Common/User/Interfaces/UserInterface.php create mode 100644 system/src/Grav/Common/User/Traits/UserTrait.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/Common/Yaml.php create mode 100644 system/src/Grav/Console/Application/Application.php create mode 100644 system/src/Grav/Console/Application/CommandLoader/PluginCommandLoader.php create mode 100644 system/src/Grav/Console/Application/GpmApplication.php create mode 100644 system/src/Grav/Console/Application/GravApplication.php create mode 100644 system/src/Grav/Console/Application/PluginApplication.php create mode 100644 system/src/Grav/Console/Cli/BackupCommand.php create mode 100644 system/src/Grav/Console/Cli/CleanCommand.php create mode 100644 system/src/Grav/Console/Cli/ClearCacheCommand.php create mode 100644 system/src/Grav/Console/Cli/ComposerCommand.php create mode 100644 system/src/Grav/Console/Cli/InstallCommand.php create mode 100644 system/src/Grav/Console/Cli/LogViewerCommand.php create mode 100644 system/src/Grav/Console/Cli/NewProjectCommand.php create mode 100644 system/src/Grav/Console/Cli/PageSystemValidatorCommand.php create mode 100644 system/src/Grav/Console/Cli/SandboxCommand.php create mode 100644 system/src/Grav/Console/Cli/SchedulerCommand.php create mode 100644 system/src/Grav/Console/Cli/SecurityCommand.php create mode 100644 system/src/Grav/Console/Cli/ServerCommand.php create mode 100644 system/src/Grav/Console/Cli/YamlLinterCommand.php create mode 100644 system/src/Grav/Console/ConsoleCommand.php create mode 100644 system/src/Grav/Console/ConsoleTrait.php create mode 100644 system/src/Grav/Console/Gpm/DirectInstallCommand.php create mode 100644 system/src/Grav/Console/Gpm/IndexCommand.php create mode 100644 system/src/Grav/Console/Gpm/InfoCommand.php create mode 100644 system/src/Grav/Console/Gpm/InstallCommand.php create mode 100644 system/src/Grav/Console/Gpm/SelfupgradeCommand.php create mode 100644 system/src/Grav/Console/Gpm/UninstallCommand.php create mode 100644 system/src/Grav/Console/Gpm/UpdateCommand.php create mode 100644 system/src/Grav/Console/Gpm/VersionCommand.php create mode 100644 system/src/Grav/Console/GpmCommand.php create mode 100644 system/src/Grav/Console/GravCommand.php create mode 100644 system/src/Grav/Console/Plugin/PluginListCommand.php create mode 100644 system/src/Grav/Console/TerminalObjects/Table.php create mode 100644 system/src/Grav/Events/BeforeSessionStartEvent.php create mode 100644 system/src/Grav/Events/FlexRegisterEvent.php create mode 100644 system/src/Grav/Events/PageEvent.php create mode 100644 system/src/Grav/Events/PermissionsRegisterEvent.php create mode 100644 system/src/Grav/Events/PluginsLoadedEvent.php create mode 100644 system/src/Grav/Events/SessionStartEvent.php create mode 100644 system/src/Grav/Events/TypesEvent.php create mode 100644 system/src/Grav/Framework/Acl/Access.php create mode 100644 system/src/Grav/Framework/Acl/Action.php create mode 100644 system/src/Grav/Framework/Acl/Permissions.php create mode 100644 system/src/Grav/Framework/Acl/PermissionsReader.php create mode 100644 system/src/Grav/Framework/Acl/RecursiveActionIterator.php create mode 100644 system/src/Grav/Framework/Cache/AbstractCache.php create mode 100644 system/src/Grav/Framework/Cache/Adapter/ChainCache.php create mode 100644 system/src/Grav/Framework/Cache/Adapter/DoctrineCache.php create mode 100644 system/src/Grav/Framework/Cache/Adapter/FileCache.php create mode 100644 system/src/Grav/Framework/Cache/Adapter/MemoryCache.php create mode 100644 system/src/Grav/Framework/Cache/Adapter/SessionCache.php create mode 100644 system/src/Grav/Framework/Cache/CacheInterface.php create mode 100644 system/src/Grav/Framework/Cache/CacheTrait.php create mode 100644 system/src/Grav/Framework/Cache/Exception/CacheException.php create mode 100644 system/src/Grav/Framework/Cache/Exception/InvalidArgumentException.php create mode 100644 system/src/Grav/Framework/Collection/AbstractFileCollection.php create mode 100644 system/src/Grav/Framework/Collection/AbstractIndexCollection.php create mode 100644 system/src/Grav/Framework/Collection/AbstractLazyCollection.php create mode 100644 system/src/Grav/Framework/Collection/ArrayCollection.php create mode 100644 system/src/Grav/Framework/Collection/CollectionInterface.php create mode 100644 system/src/Grav/Framework/Collection/FileCollection.php create mode 100644 system/src/Grav/Framework/Collection/FileCollectionInterface.php create mode 100644 system/src/Grav/Framework/Compat/Serializable.php create mode 100644 system/src/Grav/Framework/ContentBlock/ContentBlock.php create mode 100644 system/src/Grav/Framework/ContentBlock/ContentBlockInterface.php create mode 100644 system/src/Grav/Framework/ContentBlock/HtmlBlock.php create mode 100644 system/src/Grav/Framework/ContentBlock/HtmlBlockInterface.php create mode 100644 system/src/Grav/Framework/Contracts/Media/MediaObjectInterface.php create mode 100644 system/src/Grav/Framework/Contracts/Object/IdentifierInterface.php create mode 100644 system/src/Grav/Framework/Contracts/Relationships/RelationshipIdentifierInterface.php create mode 100644 system/src/Grav/Framework/Contracts/Relationships/RelationshipInterface.php create mode 100644 system/src/Grav/Framework/Contracts/Relationships/RelationshipsInterface.php create mode 100644 system/src/Grav/Framework/Contracts/Relationships/ToManyRelationshipInterface.php create mode 100644 system/src/Grav/Framework/Contracts/Relationships/ToOneRelationshipInterface.php create mode 100644 system/src/Grav/Framework/Controller/Traits/ControllerResponseTrait.php create mode 100644 system/src/Grav/Framework/DI/Container.php create mode 100644 system/src/Grav/Framework/File/AbstractFile.php create mode 100644 system/src/Grav/Framework/File/CsvFile.php create mode 100644 system/src/Grav/Framework/File/DataFile.php create mode 100644 system/src/Grav/Framework/File/File.php create mode 100644 system/src/Grav/Framework/File/Formatter/AbstractFormatter.php create mode 100644 system/src/Grav/Framework/File/Formatter/CsvFormatter.php create mode 100644 system/src/Grav/Framework/File/Formatter/FormatterInterface.php create mode 100644 system/src/Grav/Framework/File/Formatter/IniFormatter.php create mode 100644 system/src/Grav/Framework/File/Formatter/JsonFormatter.php create mode 100644 system/src/Grav/Framework/File/Formatter/MarkdownFormatter.php create mode 100644 system/src/Grav/Framework/File/Formatter/SerializeFormatter.php create mode 100644 system/src/Grav/Framework/File/Formatter/YamlFormatter.php create mode 100644 system/src/Grav/Framework/File/IniFile.php create mode 100644 system/src/Grav/Framework/File/Interfaces/FileFormatterInterface.php create mode 100644 system/src/Grav/Framework/File/Interfaces/FileInterface.php create mode 100644 system/src/Grav/Framework/File/JsonFile.php create mode 100644 system/src/Grav/Framework/File/MarkdownFile.php create mode 100644 system/src/Grav/Framework/File/YamlFile.php create mode 100644 system/src/Grav/Framework/Filesystem/Filesystem.php create mode 100644 system/src/Grav/Framework/Filesystem/Interfaces/FilesystemInterface.php create mode 100644 system/src/Grav/Framework/Flex/Flex.php create mode 100644 system/src/Grav/Framework/Flex/FlexCollection.php create mode 100644 system/src/Grav/Framework/Flex/FlexDirectory.php create mode 100644 system/src/Grav/Framework/Flex/FlexDirectoryForm.php create mode 100644 system/src/Grav/Framework/Flex/FlexForm.php create mode 100644 system/src/Grav/Framework/Flex/FlexFormFlash.php create mode 100644 system/src/Grav/Framework/Flex/FlexIdentifier.php create mode 100644 system/src/Grav/Framework/Flex/FlexIndex.php create mode 100644 system/src/Grav/Framework/Flex/FlexObject.php create mode 100644 system/src/Grav/Framework/Flex/Interfaces/FlexAuthorizeInterface.php create mode 100644 system/src/Grav/Framework/Flex/Interfaces/FlexCollectionInterface.php create mode 100644 system/src/Grav/Framework/Flex/Interfaces/FlexCommonInterface.php create mode 100644 system/src/Grav/Framework/Flex/Interfaces/FlexDirectoryFormInterface.php create mode 100644 system/src/Grav/Framework/Flex/Interfaces/FlexDirectoryInterface.php create mode 100644 system/src/Grav/Framework/Flex/Interfaces/FlexFormInterface.php create mode 100644 system/src/Grav/Framework/Flex/Interfaces/FlexIndexInterface.php create mode 100644 system/src/Grav/Framework/Flex/Interfaces/FlexInterface.php create mode 100644 system/src/Grav/Framework/Flex/Interfaces/FlexObjectFormInterface.php create mode 100644 system/src/Grav/Framework/Flex/Interfaces/FlexObjectInterface.php create mode 100644 system/src/Grav/Framework/Flex/Interfaces/FlexStorageInterface.php create mode 100644 system/src/Grav/Framework/Flex/Interfaces/FlexTranslateInterface.php create mode 100644 system/src/Grav/Framework/Flex/Pages/FlexPageCollection.php create mode 100644 system/src/Grav/Framework/Flex/Pages/FlexPageIndex.php create mode 100644 system/src/Grav/Framework/Flex/Pages/FlexPageObject.php create mode 100644 system/src/Grav/Framework/Flex/Pages/Traits/PageAuthorsTrait.php create mode 100644 system/src/Grav/Framework/Flex/Pages/Traits/PageContentTrait.php create mode 100644 system/src/Grav/Framework/Flex/Pages/Traits/PageLegacyTrait.php create mode 100644 system/src/Grav/Framework/Flex/Pages/Traits/PageRoutableTrait.php create mode 100644 system/src/Grav/Framework/Flex/Pages/Traits/PageTranslateTrait.php create mode 100644 system/src/Grav/Framework/Flex/Storage/AbstractFilesystemStorage.php create mode 100644 system/src/Grav/Framework/Flex/Storage/FileStorage.php create mode 100644 system/src/Grav/Framework/Flex/Storage/FolderStorage.php create mode 100644 system/src/Grav/Framework/Flex/Storage/SimpleStorage.php create mode 100644 system/src/Grav/Framework/Flex/Traits/FlexAuthorizeTrait.php create mode 100644 system/src/Grav/Framework/Flex/Traits/FlexMediaTrait.php create mode 100644 system/src/Grav/Framework/Flex/Traits/FlexRelatedDirectoryTrait.php create mode 100644 system/src/Grav/Framework/Flex/Traits/FlexRelationshipsTrait.php create mode 100644 system/src/Grav/Framework/Form/FormFlash.php create mode 100644 system/src/Grav/Framework/Form/FormFlashFile.php create mode 100644 system/src/Grav/Framework/Form/Interfaces/FormFactoryInterface.php create mode 100644 system/src/Grav/Framework/Form/Interfaces/FormFlashInterface.php create mode 100644 system/src/Grav/Framework/Form/Interfaces/FormInterface.php create mode 100644 system/src/Grav/Framework/Form/Traits/FormTrait.php create mode 100644 system/src/Grav/Framework/Interfaces/RenderInterface.php create mode 100644 system/src/Grav/Framework/Logger/Processors/UserProcessor.php create mode 100644 system/src/Grav/Framework/Media/Interfaces/MediaCollectionInterface.php create mode 100644 system/src/Grav/Framework/Media/Interfaces/MediaInterface.php create mode 100644 system/src/Grav/Framework/Media/Interfaces/MediaManipulationInterface.php create mode 100644 system/src/Grav/Framework/Media/Interfaces/MediaObjectInterface.php create mode 100644 system/src/Grav/Framework/Media/MediaIdentifier.php create mode 100644 system/src/Grav/Framework/Media/MediaObject.php create mode 100644 system/src/Grav/Framework/Media/UploadedMediaObject.php create mode 100644 system/src/Grav/Framework/Mime/MimeTypes.php create mode 100644 system/src/Grav/Framework/Object/Access/ArrayAccessTrait.php create mode 100644 system/src/Grav/Framework/Object/Access/NestedArrayAccessTrait.php create mode 100644 system/src/Grav/Framework/Object/Access/NestedPropertyCollectionTrait.php create mode 100644 system/src/Grav/Framework/Object/Access/NestedPropertyTrait.php create mode 100644 system/src/Grav/Framework/Object/Access/OverloadedPropertyTrait.php create mode 100644 system/src/Grav/Framework/Object/ArrayObject.php create mode 100644 system/src/Grav/Framework/Object/Base/ObjectCollectionTrait.php create mode 100644 system/src/Grav/Framework/Object/Base/ObjectTrait.php create mode 100644 system/src/Grav/Framework/Object/Collection/ObjectExpressionVisitor.php create mode 100644 system/src/Grav/Framework/Object/Identifiers/Identifier.php create mode 100644 system/src/Grav/Framework/Object/Interfaces/NestedObjectCollectionInterface.php create mode 100644 system/src/Grav/Framework/Object/Interfaces/NestedObjectInterface.php create mode 100644 system/src/Grav/Framework/Object/Interfaces/ObjectCollectionInterface.php create mode 100644 system/src/Grav/Framework/Object/Interfaces/ObjectInterface.php create mode 100644 system/src/Grav/Framework/Object/LazyObject.php create mode 100644 system/src/Grav/Framework/Object/ObjectCollection.php create mode 100644 system/src/Grav/Framework/Object/ObjectIndex.php create mode 100644 system/src/Grav/Framework/Object/Property/ArrayPropertyTrait.php create mode 100644 system/src/Grav/Framework/Object/Property/LazyPropertyTrait.php create mode 100644 system/src/Grav/Framework/Object/Property/MixedPropertyTrait.php create mode 100644 system/src/Grav/Framework/Object/Property/ObjectPropertyTrait.php create mode 100644 system/src/Grav/Framework/Object/PropertyObject.php create mode 100644 system/src/Grav/Framework/Pagination/AbstractPagination.php create mode 100644 system/src/Grav/Framework/Pagination/AbstractPaginationPage.php create mode 100644 system/src/Grav/Framework/Pagination/Interfaces/PaginationInterface.php create mode 100644 system/src/Grav/Framework/Pagination/Interfaces/PaginationPageInterface.php create mode 100644 system/src/Grav/Framework/Pagination/Pagination.php create mode 100644 system/src/Grav/Framework/Pagination/PaginationPage.php create mode 100644 system/src/Grav/Framework/Psr7/AbstractUri.php create mode 100644 system/src/Grav/Framework/Psr7/Request.php create mode 100644 system/src/Grav/Framework/Psr7/Response.php create mode 100644 system/src/Grav/Framework/Psr7/ServerRequest.php create mode 100644 system/src/Grav/Framework/Psr7/Stream.php create mode 100644 system/src/Grav/Framework/Psr7/Traits/MessageDecoratorTrait.php create mode 100644 system/src/Grav/Framework/Psr7/Traits/RequestDecoratorTrait.php create mode 100644 system/src/Grav/Framework/Psr7/Traits/ResponseDecoratorTrait.php create mode 100644 system/src/Grav/Framework/Psr7/Traits/ServerRequestDecoratorTrait.php create mode 100644 system/src/Grav/Framework/Psr7/Traits/StreamDecoratorTrait.php create mode 100644 system/src/Grav/Framework/Psr7/Traits/UploadedFileDecoratorTrait.php create mode 100644 system/src/Grav/Framework/Psr7/Traits/UriDecorationTrait.php create mode 100644 system/src/Grav/Framework/Psr7/UploadedFile.php create mode 100644 system/src/Grav/Framework/Psr7/Uri.php create mode 100644 system/src/Grav/Framework/Relationships/Relationships.php create mode 100644 system/src/Grav/Framework/Relationships/ToManyRelationship.php create mode 100644 system/src/Grav/Framework/Relationships/ToOneRelationship.php create mode 100644 system/src/Grav/Framework/Relationships/Traits/RelationshipTrait.php create mode 100644 system/src/Grav/Framework/RequestHandler/Exception/InvalidArgumentException.php create mode 100644 system/src/Grav/Framework/RequestHandler/Exception/NotFoundException.php create mode 100644 system/src/Grav/Framework/RequestHandler/Exception/NotHandledException.php create mode 100644 system/src/Grav/Framework/RequestHandler/Exception/PageExpiredException.php create mode 100644 system/src/Grav/Framework/RequestHandler/Exception/RequestException.php create mode 100644 system/src/Grav/Framework/RequestHandler/Middlewares/Exceptions.php create mode 100644 system/src/Grav/Framework/RequestHandler/Middlewares/MultipartRequestSupport.php create mode 100644 system/src/Grav/Framework/RequestHandler/RequestHandler.php create mode 100644 system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php create mode 100644 system/src/Grav/Framework/Route/Route.php create mode 100644 system/src/Grav/Framework/Route/RouteFactory.php create mode 100644 system/src/Grav/Framework/Session/Exceptions/SessionException.php create mode 100644 system/src/Grav/Framework/Session/Messages.php create mode 100644 system/src/Grav/Framework/Session/Session.php create mode 100644 system/src/Grav/Framework/Session/SessionInterface.php create mode 100644 system/src/Grav/Framework/Uri/Uri.php create mode 100644 system/src/Grav/Framework/Uri/UriFactory.php create mode 100644 system/src/Grav/Framework/Uri/UriPartsFilter.php create mode 100644 system/src/Grav/Installer/Install.php create mode 100644 system/src/Grav/Installer/InstallException.php create mode 100644 system/src/Grav/Installer/VersionUpdate.php create mode 100644 system/src/Grav/Installer/VersionUpdater.php create mode 100644 system/src/Grav/Installer/Versions.php create mode 100644 system/src/Grav/Installer/YamlUpdater.php create mode 100644 system/src/Grav/Installer/updates/1.7.0_2020-11-20_1.php create mode 100755 system/src/Twig/DeferredExtension/DeferredBlockNode.php create mode 100644 system/src/Twig/DeferredExtension/DeferredDeclareNode.php create mode 100644 system/src/Twig/DeferredExtension/DeferredExtension.php create mode 100644 system/src/Twig/DeferredExtension/DeferredInitializeNode.php create mode 100755 system/src/Twig/DeferredExtension/DeferredNode.php create mode 100644 system/src/Twig/DeferredExtension/DeferredNodeVisitor.php create mode 100644 system/src/Twig/DeferredExtension/DeferredNodeVisitorCompat.php create mode 100644 system/src/Twig/DeferredExtension/DeferredResolveNode.php create mode 100644 system/src/Twig/DeferredExtension/DeferredTokenParser.php create mode 100644 system/templates/default.html.twig create mode 100644 system/templates/external.html.twig create mode 100644 system/templates/flex/404.html.twig create mode 100644 system/templates/flex/_default/collection/debug.html.twig create mode 100644 system/templates/flex/_default/object/debug.html.twig create mode 100644 system/templates/modular/default.html.twig create mode 100644 system/templates/partials/messages.html.twig create mode 100644 system/templates/partials/metadata.html.twig create mode 100644 tests/_bootstrap.php create mode 100644 tests/_support/AcceptanceTester.php create mode 100644 tests/_support/FunctionalTester.php create mode 100644 tests/_support/Helper/Acceptance.php create mode 100644 tests/_support/Helper/Functional.php create mode 100644 tests/_support/Helper/Unit.php create mode 100644 tests/_support/UnitTester.php create mode 100644 tests/acceptance.suite.yml create mode 100644 tests/acceptance/_bootstrap.php create mode 100644 tests/fake/nested-site/user/pages/01.item1/01.item1-1/01.item1-1-1/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/01.item1-1/02.item1-1-2/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/01.item1-1/03.item1-1-3/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/01.item1-1/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/02.item1-2/01.item1-2-1/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/02.item1-2/02.item1-2-2/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/02.item1-2/03.item1-2-3/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/02.item1-2/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/03.item1-3/01.item1-3-1/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/03.item1-3/02.item1-3-2/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/03.item1-3/03.item1-3-3/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/03.item1-3/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/default.md create mode 100644 tests/fake/nested-site/user/pages/01.item1/existing-file.zip create mode 100644 tests/fake/nested-site/user/pages/01.item1/home-cache-image.jpg create mode 100644 tests/fake/nested-site/user/pages/01.item1/home-sample-image.jpg create mode 100644 tests/fake/nested-site/user/pages/02.item2/01.item2-1/01.item2-1-1/default.md create mode 100644 tests/fake/nested-site/user/pages/02.item2/01.item2-1/02.item2-1-2/default.md create mode 100644 tests/fake/nested-site/user/pages/02.item2/01.item2-1/03.item2-1-3/default.md create mode 100644 tests/fake/nested-site/user/pages/02.item2/01.item2-1/default.md create mode 100644 tests/fake/nested-site/user/pages/02.item2/02.item2-2/01.item2-2-1/default.md create mode 100644 tests/fake/nested-site/user/pages/02.item2/02.item2-2/02.item2-2-2/default.md create mode 100644 tests/fake/nested-site/user/pages/02.item2/02.item2-2/03.item2-2-3/default.md create mode 100644 tests/fake/nested-site/user/pages/02.item2/02.item2-2/cache-image.jpg create mode 100644 tests/fake/nested-site/user/pages/02.item2/02.item2-2/default.md create mode 100644 tests/fake/nested-site/user/pages/02.item2/02.item2-2/existing-file.zip create mode 100644 tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg create mode 100644 tests/fake/nested-site/user/pages/02.item2/03.item2-3/01.item2-3-1/default.md create mode 100644 tests/fake/nested-site/user/pages/02.item2/03.item2-3/02.item2-3-2/default.md create mode 100644 tests/fake/nested-site/user/pages/02.item2/03.item2-3/03.item2-3-3/default.md create mode 100644 tests/fake/nested-site/user/pages/02.item2/03.item2-3/default.md create mode 100644 tests/fake/nested-site/user/pages/02.item2/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/01.item3-1/01.item3-1-1/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/01.item3-1/02.item3-1-2/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/01.item3-1/03.item3-1-3/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/01.item3-1/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/02.item3-2/01.item3-2-1/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/02.item3-2/02.item3-2-2/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/02.item3-2/03.item3-2-3/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/02.item3-2/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/03.item3-3/01.item3-3-1/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/03.item3-3/02.item3-3-2/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/03.item3-3/03.item3-3-3/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/03.item3-3/default.md create mode 100644 tests/fake/nested-site/user/pages/03.item3/default.md create mode 100644 tests/fake/simple-site/user/pages/01.home/default.md create mode 100644 tests/fake/simple-site/user/pages/02.blog/blog.md create mode 100644 tests/fake/simple-site/user/pages/02.blog/post-one/item.md create mode 100644 tests/fake/simple-site/user/pages/02.blog/post-two/item.md create mode 100644 tests/fake/simple-site/user/pages/03.about/default.md create mode 100644 tests/fake/simple-site/user/pages/04.page-translated/default.en.md create mode 100644 tests/fake/simple-site/user/pages/04.page-translated/default.fr.md create mode 100644 tests/fake/simple-site/user/pages/05.translatedlong/part2/default.en.md create mode 100644 tests/fake/simple-site/user/pages/05.translatedlong/part2/default.fr.md create mode 100644 tests/fake/single-page-translated/user/pages/01.simple-page/default.en.md create mode 100644 tests/fake/single-page-translated/user/pages/01.simple-page/default.fr.md create mode 100644 tests/fake/single-pages/01.simple-page/default.md create mode 100644 tests/functional.suite.yml create mode 100644 tests/functional/Grav/Console/DirectInstallCommandTest.php create mode 100644 tests/functional/_bootstrap.php create mode 100644 tests/phpstan/classes/Toolbox/UniformResourceLocatorExtension.php create mode 100644 tests/phpstan/extension.neon create mode 100644 tests/phpstan/phpstan-bootstrap.php create mode 100644 tests/phpstan/phpstan.neon create mode 100644 tests/phpstan/plugins-bootstrap.php create mode 100644 tests/phpstan/plugins.neon create mode 100644 tests/unit.suite.yml create mode 100644 tests/unit/Grav/Common/AssetsTest.php create mode 100644 tests/unit/Grav/Common/BrowserTest.php create mode 100644 tests/unit/Grav/Common/ComposerTest.php create mode 100644 tests/unit/Grav/Common/Data/BlueprintTest.php create mode 100644 tests/unit/Grav/Common/GPM/GPMTest.php create mode 100644 tests/unit/Grav/Common/Helpers/ExcerptsTest.php create mode 100644 tests/unit/Grav/Common/InflectorTest.php create mode 100644 tests/unit/Grav/Common/Language/LanguageCodesTest.php create mode 100644 tests/unit/Grav/Common/Markdown/ParsedownTest.php create mode 100644 tests/unit/Grav/Common/Page/PagesTest.php create mode 100644 tests/unit/Grav/Common/Twig/Extensions/GravExtensionTest.php create mode 100644 tests/unit/Grav/Common/UriTest.php create mode 100644 tests/unit/Grav/Common/UtilsTest.php create mode 100644 tests/unit/Grav/Console/Gpm/InstallCommandTest.php create mode 100644 tests/unit/Grav/Framework/File/Formatter/CsvFormatterTest.php create mode 100644 tests/unit/Grav/Framework/Filesystem/FilesystemTest.php create mode 100644 tests/unit/_bootstrap.php create mode 100644 tests/unit/data/blueprints/strict.yaml create mode 100644 tmp/.gitkeep create mode 100644 user/accounts/.gitkeep create mode 100644 user/config/media.yaml create mode 100644 user/config/site.yaml create mode 100644 user/config/system.yaml create mode 100644 user/config/themes/quark.yaml create mode 100644 user/data/.gitkeep create mode 100644 user/pages/01.home/bulle_texte.png create mode 100644 user/pages/01.home/default.md create mode 100644 user/pages/01.home/oeil.gif create mode 100644 user/pages/01.home/oeil_colere.png create mode 100644 user/pages/01.home/robot_detruit.png create mode 100644 user/pages/01.home/robot_guerrier.png create mode 100644 user/pages/01.home/robot_neuf.png create mode 100644 user/plugins/.gitkeep create mode 100644 user/themes/.gitkeep create mode 100644 user/themes/test/CHANGELOG.md create mode 100644 user/themes/test/LICENSE create mode 100644 user/themes/test/README.md create mode 100644 user/themes/test/assets/quark-screenshots.jpg create mode 100644 user/themes/test/blueprints.yaml create mode 100644 user/themes/test/blueprints/blog.yaml create mode 100644 user/themes/test/blueprints/default.yaml create mode 100644 user/themes/test/blueprints/item.yaml create mode 100644 user/themes/test/blueprints/modular.yaml create mode 100644 user/themes/test/blueprints/modular/features.yaml create mode 100644 user/themes/test/blueprints/modular/hero.yaml create mode 100644 user/themes/test/blueprints/modular/text.yaml create mode 100644 user/themes/test/blueprints/partials/blog-bits.yaml create mode 100755 user/themes/test/css-compiled/spectre-exp.css create mode 100755 user/themes/test/css-compiled/spectre-exp.min.css create mode 100755 user/themes/test/css-compiled/spectre-icons.css create mode 100755 user/themes/test/css-compiled/spectre-icons.min.css create mode 100755 user/themes/test/css-compiled/spectre.css create mode 100755 user/themes/test/css-compiled/spectre.min.css create mode 100644 user/themes/test/css-compiled/theme.css create mode 100644 user/themes/test/css-compiled/theme.min.css create mode 100755 user/themes/test/css/bricklayer.css create mode 100644 user/themes/test/css/custom.css create mode 100644 user/themes/test/css/line-awesome.min.css create mode 100644 user/themes/test/fonts/line-awesome.eot create mode 100644 user/themes/test/fonts/line-awesome.svg create mode 100644 user/themes/test/fonts/line-awesome.ttf create mode 100644 user/themes/test/fonts/line-awesome.woff create mode 100644 user/themes/test/fonts/line-awesome.woff2 create mode 100755 user/themes/test/gulpfile.js create mode 100644 user/themes/test/images/favicon.png create mode 100644 user/themes/test/images/grav-logo.svg create mode 100644 user/themes/test/images/logo/.gitkeep create mode 100644 user/themes/test/images/logo/oeil.gif create mode 100755 user/themes/test/js/bricklayer.min.js create mode 100755 user/themes/test/js/jquery.treemenu.js create mode 100644 user/themes/test/js/perso.js create mode 100644 user/themes/test/js/scopedQuerySelectorShim.min.js create mode 100644 user/themes/test/js/singlepagenav.min.js create mode 100644 user/themes/test/js/site.js create mode 100644 user/themes/test/js/smooth-scroll.min.js create mode 100644 user/themes/test/languages.yaml create mode 100755 user/themes/test/package.json create mode 100644 user/themes/test/screenshot.jpg create mode 100755 user/themes/test/scss/spectre-exp.scss create mode 100755 user/themes/test/scss/spectre-icons.scss create mode 100755 user/themes/test/scss/spectre.scss create mode 100755 user/themes/test/scss/spectre/_accordions.scss create mode 100755 user/themes/test/scss/spectre/_animations.scss create mode 100755 user/themes/test/scss/spectre/_asian.scss create mode 100755 user/themes/test/scss/spectre/_autocomplete.scss create mode 100755 user/themes/test/scss/spectre/_avatars.scss create mode 100755 user/themes/test/scss/spectre/_badges.scss create mode 100755 user/themes/test/scss/spectre/_bars.scss create mode 100755 user/themes/test/scss/spectre/_base.scss create mode 100755 user/themes/test/scss/spectre/_breadcrumbs.scss create mode 100755 user/themes/test/scss/spectre/_buttons.scss create mode 100755 user/themes/test/scss/spectre/_calendars.scss create mode 100755 user/themes/test/scss/spectre/_cards.scss create mode 100755 user/themes/test/scss/spectre/_carousels.scss create mode 100755 user/themes/test/scss/spectre/_chips.scss create mode 100755 user/themes/test/scss/spectre/_codes.scss create mode 100755 user/themes/test/scss/spectre/_comparison-sliders.scss create mode 100755 user/themes/test/scss/spectre/_dropdowns.scss create mode 100755 user/themes/test/scss/spectre/_empty.scss create mode 100755 user/themes/test/scss/spectre/_filters.scss create mode 100755 user/themes/test/scss/spectre/_forms.scss create mode 100755 user/themes/test/scss/spectre/_hero.scss create mode 100755 user/themes/test/scss/spectre/_icons.scss create mode 100755 user/themes/test/scss/spectre/_labels.scss create mode 100755 user/themes/test/scss/spectre/_layout.scss create mode 100755 user/themes/test/scss/spectre/_media.scss create mode 100755 user/themes/test/scss/spectre/_menus.scss create mode 100755 user/themes/test/scss/spectre/_meters.scss create mode 100755 user/themes/test/scss/spectre/_mixins.scss create mode 100755 user/themes/test/scss/spectre/_modals.scss create mode 100755 user/themes/test/scss/spectre/_navbar.scss create mode 100755 user/themes/test/scss/spectre/_navs.scss create mode 100755 user/themes/test/scss/spectre/_normalize.scss create mode 100755 user/themes/test/scss/spectre/_off-canvas.scss create mode 100755 user/themes/test/scss/spectre/_pagination.scss create mode 100755 user/themes/test/scss/spectre/_panels.scss create mode 100755 user/themes/test/scss/spectre/_parallax.scss create mode 100755 user/themes/test/scss/spectre/_popovers.scss create mode 100755 user/themes/test/scss/spectre/_progress.scss create mode 100755 user/themes/test/scss/spectre/_sliders.scss create mode 100755 user/themes/test/scss/spectre/_steps.scss create mode 100755 user/themes/test/scss/spectre/_tables.scss create mode 100755 user/themes/test/scss/spectre/_tabs.scss create mode 100755 user/themes/test/scss/spectre/_tiles.scss create mode 100755 user/themes/test/scss/spectre/_timelines.scss create mode 100755 user/themes/test/scss/spectre/_toasts.scss create mode 100755 user/themes/test/scss/spectre/_tooltips.scss create mode 100755 user/themes/test/scss/spectre/_typography.scss create mode 100755 user/themes/test/scss/spectre/_utilities.scss create mode 100755 user/themes/test/scss/spectre/_variables.scss create mode 100755 user/themes/test/scss/spectre/_viewer-360.scss create mode 100755 user/themes/test/scss/spectre/icons/_icons-action.scss create mode 100755 user/themes/test/scss/spectre/icons/_icons-core.scss create mode 100755 user/themes/test/scss/spectre/icons/_icons-navigation.scss create mode 100755 user/themes/test/scss/spectre/icons/_icons-object.scss create mode 100755 user/themes/test/scss/spectre/mixins/_avatar.scss create mode 100755 user/themes/test/scss/spectre/mixins/_button.scss create mode 100755 user/themes/test/scss/spectre/mixins/_clearfix.scss create mode 100755 user/themes/test/scss/spectre/mixins/_color.scss create mode 100755 user/themes/test/scss/spectre/mixins/_label.scss create mode 100755 user/themes/test/scss/spectre/mixins/_position.scss create mode 100755 user/themes/test/scss/spectre/mixins/_shadow.scss create mode 100755 user/themes/test/scss/spectre/mixins/_text.scss create mode 100755 user/themes/test/scss/spectre/mixins/_toast.scss create mode 100755 user/themes/test/scss/spectre/spectre-exp.scss create mode 100755 user/themes/test/scss/spectre/spectre-icons.scss create mode 100755 user/themes/test/scss/spectre/spectre.scss create mode 100755 user/themes/test/scss/spectre/utilities/_colors.scss create mode 100755 user/themes/test/scss/spectre/utilities/_cursors.scss create mode 100755 user/themes/test/scss/spectre/utilities/_display.scss create mode 100755 user/themes/test/scss/spectre/utilities/_divider.scss create mode 100755 user/themes/test/scss/spectre/utilities/_loading.scss create mode 100755 user/themes/test/scss/spectre/utilities/_position.scss create mode 100755 user/themes/test/scss/spectre/utilities/_shapes.scss create mode 100755 user/themes/test/scss/spectre/utilities/_text.scss create mode 100644 user/themes/test/scss/theme.scss create mode 100644 user/themes/test/scss/theme/_animation.scss create mode 100644 user/themes/test/scss/theme/_blog.scss create mode 100644 user/themes/test/scss/theme/_extensions.scss create mode 100644 user/themes/test/scss/theme/_fonts.scss create mode 100644 user/themes/test/scss/theme/_footer.scss create mode 100644 user/themes/test/scss/theme/_forms.scss create mode 100644 user/themes/test/scss/theme/_framework.scss create mode 100644 user/themes/test/scss/theme/_header.scss create mode 100644 user/themes/test/scss/theme/_menu.scss create mode 100644 user/themes/test/scss/theme/_mixins.scss create mode 100644 user/themes/test/scss/theme/_mobile.scss create mode 100644 user/themes/test/scss/theme/_onepage.scss create mode 100644 user/themes/test/scss/theme/_typography.scss create mode 100644 user/themes/test/scss/theme/_variables.scss create mode 100644 user/themes/test/templates/blocks/base.html.twig create mode 100644 user/themes/test/templates/blog.html.twig create mode 100644 user/themes/test/templates/comments.html.twig create mode 100644 user/themes/test/templates/default.html.twig create mode 100644 user/themes/test/templates/error.html.twig create mode 100644 user/themes/test/templates/forms/fields/checkbox/checkbox.html.twig create mode 100644 user/themes/test/templates/forms/fields/checkboxes/checkboxes.html.twig create mode 100644 user/themes/test/templates/forms/fields/radio/radio.html.twig create mode 100644 user/themes/test/templates/forms/fields/switch/switch.html.twig create mode 100644 user/themes/test/templates/item.html.twig create mode 100644 user/themes/test/templates/macros/macros.html.twig create mode 100644 user/themes/test/templates/modular.html.twig create mode 100644 user/themes/test/templates/modular/features.html.twig create mode 100644 user/themes/test/templates/modular/gallery.html.twig create mode 100644 user/themes/test/templates/modular/hero.html.twig create mode 100644 user/themes/test/templates/modular/text.html.twig create mode 100644 user/themes/test/templates/partials/archives.html.twig create mode 100644 user/themes/test/templates/partials/base.html.twig create mode 100644 user/themes/test/templates/partials/blog-item.html.twig create mode 100644 user/themes/test/templates/partials/blog-list-item.html.twig create mode 100644 user/themes/test/templates/partials/blog/date.html.twig create mode 100644 user/themes/test/templates/partials/blog/page-summary.html.twig create mode 100644 user/themes/test/templates/partials/blog/taxonomy.html.twig create mode 100644 user/themes/test/templates/partials/blog/title.html.twig create mode 100644 user/themes/test/templates/partials/footer.html.twig create mode 100644 user/themes/test/templates/partials/form-messages.html.twig create mode 100644 user/themes/test/templates/partials/hero.html.twig create mode 100644 user/themes/test/templates/partials/layout.html.twig create mode 100644 user/themes/test/templates/partials/logo.html.twig create mode 100644 user/themes/test/templates/partials/messages.html.twig create mode 100644 user/themes/test/templates/partials/navigation.html.twig create mode 100644 user/themes/test/templates/partials/relatedpages.html.twig create mode 100644 user/themes/test/templates/partials/sidebar.html.twig create mode 100644 user/themes/test/templates/partials/taxonomylist.html.twig create mode 100644 user/themes/test/test.php create mode 100644 user/themes/test/test.yaml create mode 100644 user/themes/test/thumbnail.jpg create mode 100644 user/themes/test/yarn.lock create mode 100644 webserver-configs/Caddyfile create mode 100644 webserver-configs/Caddyfile-0.8.x create mode 100644 webserver-configs/htaccess.txt create mode 100644 webserver-configs/lighttpd.conf create mode 100644 webserver-configs/nginx.conf create mode 100644 webserver-configs/web.config diff --git a/.dependencies b/.dependencies new file mode 100644 index 0000000..86f4a79 --- /dev/null +++ b/.dependencies @@ -0,0 +1,34 @@ +git: + problems: + url: https://github.com/getgrav/grav-plugin-problems + path: user/plugins/problems + branch: master + error: + url: https://github.com/getgrav/grav-plugin-error + path: user/plugins/error + branch: master + markdown-notices: + url: https://github.com/getgrav/grav-plugin-markdown-notices + path: user/plugins/markdown-notices + branch: master + quark: + url: https://github.com/getgrav/grav-theme-quark + path: user/themes/quark + branch: master +links: + problems: + src: grav-plugin-problems + path: user/plugins/problems + scm: github + error: + src: grav-plugin-error + path: user/plugins/error + scm: github + markdown-notices: + src: grav-plugin-markdown-notices + path: user/plugins/markdown-notices + scm: github + quark: + src: grav-theme-quark + path: user/themes/quark + scm: github diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..bb34874 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +# 2 space indentation +[*.{yaml,yml,vue,js,css}] +indent_size = 2 diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..e84f52b --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,8 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: grav +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +custom: # Replace with a single custom sponsorship URL diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..6105124 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,81 @@ +name: Release Builds + +on: + release: + types: [published] + +permissions: {} + +jobs: + build: + permissions: + contents: write # for release creation (svenstaro/upload-release-action) + + if: "!github.event.release.prerelease" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + ref: ${{ github.ref }} + + - name: Extract Tag + run: echo "PACKAGE_VERSION=${{ github.ref }}" >> $GITHUB_ENV + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 7.3 + extensions: opcache, gd + tools: composer:v2 + coverage: none + env: + COMPOSER_TOKEN: ${{ secrets.GLOBAL_TOKEN }} + + - name: Install Dependencies + run: | + sudo apt-get -y update -qq < /dev/null > /dev/null + sudo apt-get -y install -qq git zip < /dev/null > /dev/null + + - name: Retrieval of Builder Scripts + run: | + # Real Grav URL + curl --silent -H "Authorization: token ${{ secrets.GLOBAL_TOKEN }}" -H "Accept: application/vnd.github.v3.raw" ${{ secrets.BUILD_SCRIPT_URL }} --output build-grav.sh + + # Development Local URL + # curl ${{ secrets.BUILD_SCRIPT_URL }} --output build-grav.sh + + - name: Grav Builder + run: | + bash ./build-grav.sh + + - name: Upload packages to release + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + tag: ${{ env.PACKAGE_VERSION }} + file: ./grav-dist/*.zip + overwrite: true + file_glob: true + + slack: + permissions: + actions: read # to list jobs for workflow run (technote-space/workflow-conclusion-action) + + name: Slack + needs: build + runs-on: ubuntu-latest + if: always() + steps: + - uses: technote-space/workflow-conclusion-action@v2 + - uses: 8398a7/action-slack@v3 + with: + status: failure + fields: repo,message,author,action + icon_emoji: ':octocat:' + author_name: 'Github Action Build' + text: '🚚 Automated Build Failure' + env: + GITHUB_TOKEN: ${{ secrets.GLOBAL_TOKEN }} + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + if: env.WORKFLOW_CONCLUSION == 'failure' diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000..7b705eb --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,68 @@ +name: PHP Tests + +on: + push: + branches: [ develop ] + pull_request: + branches: [ develop ] + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + unit-tests: + strategy: + matrix: + php: ['8.3', '8.2', '8.1', '8.0', '7.4', '7.3'] + os: [ubuntu-latest] + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP ${{ matrix.php }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: opcache, gd + tools: composer:v2 + coverage: none + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --prefer-dist --no-progress + + - name: Run test suite + run: vendor/bin/codecept run + +# slack: +# name: Slack +# needs: unit-tests +# runs-on: ubuntu-latest +# if: always() +# steps: +# - uses: technote-space/workflow-conclusion-action@v2 +# - uses: 8398a7/action-slack@v3 +# with: +# status: failure +# fields: repo,message,author,action +# icon_emoji: ':octocat:' +# author_name: 'Github Action Tests' +# text: '💥 Automated Test Failure' +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} +# if: env.WORKFLOW_CONCLUSION == 'failure' diff --git a/.github/workflows/trigger-skeletons.yml b/.github/workflows/trigger-skeletons.yml new file mode 100644 index 0000000..b42b963 --- /dev/null +++ b/.github/workflows/trigger-skeletons.yml @@ -0,0 +1,48 @@ +name: Trigger Skeletons Build + +on: + workflow_dispatch: + inputs: + version: + description: 'Which Grav release to use' + required: true + default: 'latest' + admin: + description: 'Create also a package with Admin' + required: true + default: true + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + build: + runs-on: ubuntu-latest + env: + WORKFLOW: "build-skeleton.yml" + AUTH: ":${{secrets.GLOBAL_TOKEN}}" + steps: + - uses: actions/checkout@v2 + - name: Make it rain ☔️ + run: | + SKELETONS=`curl -s "${{secrets.SKELETONS_JSON_LIST}}"` + echo "$SKELETONS" | jq -cr '.[]' | while read SKELETON; do + KEY=$(echo "$SKELETON" | jq -cr 'keys[0]') + VERSION=$(echo "$SKELETON" | jq -cr '.[]') + URL="https://api.github.com/repos/${KEY}/actions/workflows/${WORKFLOW}/dispatches" + + curl -X POST \ + -u "${AUTH}" \ + -H "Accept: application/vnd.github.everest-preview+json" \ + -H "Content-Type: application/json" \ + -sS \ + ${URL} \ + --data '{ "ref": "develop", + "inputs": { + "tag": "'"$VERSION"'", + "version": "'"$INPUT_VERSION"'", + "admin": "'"$INPUT_ADMIN"'" + } + }' > /dev/null + echo "Dispatched Worfklow for ${KEY}@$VERSION" + done diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1328a78 --- /dev/null +++ b/.gitignore @@ -0,0 +1,51 @@ +# Composer +.composer +vendor/* +!*/vendor/* + +# Sass +.sass-cache + +# Grav Specific +backup/* +!backup/.* +cache/* +!cache/.* +assets/* +!assets/.* +logs/* +!logs/.* +images/* +!images/.* +user/accounts/* +!user/accounts/.* +user/data/* +!user/data/.* +user/plugins/* +!user/plugins/.* +user/themes/* +!user/themes/test +!user/themes/.* +user/**/config/security.yaml + +# Environments +.env +.gravenv + +# OS Generated +.DS_Store* +ehthumbs.db +Icon? +Thumbs.db +*.swp + +# phpstorm +.idea/* + +# testing stuff +tests/_output/* +tests/_support/_generated/* +tests/cache/* +tests/error.log +system/templates/testing/* +/user/config/versions.yaml diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..098c582 --- /dev/null +++ b/.htaccess @@ -0,0 +1,78 @@ + + +RewriteEngine On + +## Begin RewriteBase +# If you are getting 500 or 404 errors on subpages, you may have to uncomment the RewriteBase entry +# You should change the '/' to your appropriate subfolder. For example if you have +# your Grav install at the root of your site '/' should work, else it might be something +# along the lines of: RewriteBase / +## + +# RewriteBase / + +## End - RewriteBase + +## Begin - X-Forwarded-Proto +# In some hosted or load balanced environments, SSL negotiation happens upstream. +# In order for Grav to recognize the connection as secure, you need to uncomment +# the following lines. +# +# RewriteCond %{HTTP:X-Forwarded-Proto} https +# RewriteRule .* - [E=HTTPS:on] +# +## End - X-Forwarded-Proto + +## Begin - Exploits +# If you experience problems on your site block out the operations listed below +# This attempts to block the most common type of exploit `attempts` to Grav +# +# Block out any script trying to use twig tags in URL. +RewriteCond %{REQUEST_URI} ({{|}}|{%|%}) [OR] +RewriteCond %{QUERY_STRING} ({{|}}|{%25|%25}) [OR] +# Block out any script trying to base64_encode data within the URL. +RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR] +# Block out any script that includes a + markdown: false + + status_enhanced: + type: display + label: + content: | + + + modern_health: + type: display + label: Health Status + content: | +
+
Checking health...
+
+ + markdown: false + + trigger_methods: + type: display + label: Active Triggers + content: | +
+
Checking triggers...
+
+ + markdown: false + + jobs_tab: + type: tab + title: PLUGIN_ADMIN.SCHEDULER_JOBS + + fields: + jobs_title: + type: section + title: PLUGIN_ADMIN.SCHEDULER_JOBS + underline: true + + custom_jobs: + type: list + style: vertical + label: + classes: cron-job-list compact + key: id + fields: + .id: + type: key + label: ID + placeholder: 'process-name' + validate: + required: true + pattern: '[a-zа-я0-9_\-]+' + max: 20 + message: 'ID must be lowercase with dashes/underscores only and less than 20 characters' + .command: + type: text + label: PLUGIN_ADMIN.COMMAND + placeholder: 'ls' + validate: + required: true + .args: + type: text + label: PLUGIN_ADMIN.EXTRA_ARGUMENTS + placeholder: '-lah' + .at: + type: text + wrapper_classes: cron-selector + label: PLUGIN_ADMIN.SCHEDULER_RUNAT + help: PLUGIN_ADMIN.SCHEDULER_RUNAT_HELP + placeholder: '* * * * *' + validate: + required: true + .output: + type: text + label: PLUGIN_ADMIN.SCHEDULER_OUTPUT + help: PLUGIN_ADMIN.SCHEDULER_OUTPUT_HELP + placeholder: 'logs/ls-cron.out' + .output_mode: + type: select + label: PLUGIN_ADMIN.SCHEDULER_OUTPUT_TYPE + help: PLUGIN_ADMIN.SCHEDULER_OUTPUT_TYPE_HELP + default: append + options: + append: Append + overwrite: Overwrite + .email: + type: text + label: PLUGIN_ADMIN.SCHEDULER_EMAIL + help: PLUGIN_ADMIN.SCHEDULER_EMAIL_HELP + placeholder: 'notifications@yoursite.com' + + modern_tab: + type: tab + title: Advanced Features + + fields: + workers_section: + type: section + title: Worker Configuration + underline: true + + fields: + modern.workers: + type: number + label: Concurrent Workers + help: Number of jobs that can run simultaneously (1 = sequential) + default: 4 + size: x-small + append: workers + validate: + type: int + min: 1 + max: 10 + + retry_section: + type: section + title: Retry Configuration + underline: true + + fields: + modern.retry.enabled: + type: toggle + label: Enable Job Retry + help: Automatically retry failed jobs + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + modern.retry.max_attempts: + type: number + label: Maximum Retry Attempts + help: Maximum number of times to retry a failed job + default: 3 + size: x-small + append: retries + validate: + type: int + min: 1 + max: 10 + + modern.retry.backoff: + type: select + label: Retry Backoff Strategy + help: How to calculate delay between retries + default: exponential + options: + linear: Linear (fixed delay) + exponential: Exponential (increasing delay) + + queue_section: + type: section + title: Queue Configuration + underline: true + + fields: + modern.queue.path: + type: text + label: Queue Storage Path + help: Where to store queued jobs + default: 'user-data://scheduler/queue' + placeholder: 'user-data://scheduler/queue' + + modern.queue.max_size: + type: number + label: Maximum Queue Size + help: Maximum number of jobs that can be queued + default: 1000 + size: x-small + append: jobs + validate: + type: int + min: 100 + max: 10000 + + history_section: + type: section + title: Job History + underline: true + + fields: + modern.history.enabled: + type: toggle + label: Enable Job History + help: Track execution history for all jobs + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + modern.history.retention_days: + type: number + label: History Retention (days) + help: How long to keep job history + default: 30 + size: x-small + append: days + validate: + type: int + min: 1 + max: 365 + + webhook_section: + type: section + title: Webhook Configuration + underline: true + + fields: + webhook_plugin_status: + type: webhook-status + label: + modern.webhook.enabled: + type: toggle + label: Enable Webhook Triggers + help: Allow triggering scheduler via HTTP webhook + highlight: 0 + default: 0 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + modern.webhook.token: + type: text + label: Webhook Security Token + help: Secret token for authenticating webhook requests. Keep this secret! + placeholder: 'Click Generate to create a secure token' + autocomplete: 'off' + + webhook_token_generate: + type: display + label: + content: | +
+ +
+ + markdown: false + + modern.webhook.path: + type: text + label: Webhook Path + help: URL path for webhook endpoint + default: '/scheduler/webhook' + placeholder: '/scheduler/webhook' + + health_section: + type: section + title: Health Check Configuration + underline: true + + fields: + modern.health.enabled: + type: toggle + label: Enable Health Check + help: Provide health status endpoint for monitoring + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + modern.health.path: + type: text + label: Health Check Path + help: URL path for health check endpoint + default: '/scheduler/health' + placeholder: '/scheduler/health' + + webhook_usage: + type: section + title: Usage Examples + underline: true + + fields: + webhook_examples: + type: display + label: + content: | + +
+ + +
+

How to use webhooks:

+ +
+ +
+ +
Copy
+
+
+ +
+ +
+ +
Copy
+
+
+ +
+ +
+ +
Copy
+
+
+ +
+

GitHub Actions example:

+
- name: Trigger Scheduler
+                                                  run: |
+                                                    curl -X POST ${{ secrets.SITE_URL }}/scheduler/webhook \
+                                                      -H "Authorization: Bearer ${{ secrets.WEBHOOK_TOKEN }}"
+
+
+
+ markdown: false + + + + diff --git a/system/blueprints/config/security.yaml b/system/blueprints/config/security.yaml new file mode 100644 index 0000000..4e93e9b --- /dev/null +++ b/system/blueprints/config/security.yaml @@ -0,0 +1,119 @@ +title: PLUGIN_ADMIN.SECURITY + +form: + validation: loose + fields: + + xss_section: + type: section + title: PLUGIN_ADMIN.XSS_SECURITY + underline: true + + xss_whitelist: + type: selectize + size: large + label: PLUGIN_ADMIN.XSS_WHITELIST_PERMISSIONS + help: PLUGIN_ADMIN.XSS_WHITELIST_PERMISSIONS_HELP + placeholder: 'admin.super' + classes: fancy + validate: + type: commalist + + xss_enabled.on_events: + type: toggle + label: PLUGIN_ADMIN.XSS_ON_EVENTS + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + default: true + validate: + type: bool + + xss_enabled.invalid_protocols: + type: toggle + label: PLUGIN_ADMIN.XSS_INVALID_PROTOCOLS + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + default: true + validate: + type: bool + + xss_invalid_protocols: + type: selectize + size: large + label: PLUGIN_ADMIN.XSS_INVALID_PROTOCOLS_LIST + classes: fancy + validate: + type: commalist + + xss_enabled.moz_binding: + type: toggle + label: PLUGIN_ADMIN.XSS_MOZ_BINDINGS + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + default: true + validate: + type: bool + + xss_enabled.html_inline_styles: + type: toggle + label: PLUGIN_ADMIN.XSS_HTML_INLINE_STYLES + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + default: true + validate: + type: bool + + xss_enabled.dangerous_tags: + type: toggle + label: PLUGIN_ADMIN.XSS_DANGEROUS_TAGS + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + default: true + validate: + type: bool + + xss_dangerous_tags: + type: selectize + size: large + label: PLUGIN_ADMIN.XSS_DANGEROUS_TAGS_LIST + classes: fancy + validate: + type: commalist + + uploads_section: + type: section + title: PLUGIN_ADMIN.UPLOADS_SECURITY + underline: true + + + uploads_dangerous_extensions: + type: selectize + size: large + label: PLUGIN_ADMIN.UPLOADS_DANGEROUS_EXTENSIONS + help: PLUGIN_ADMIN.UPLOADS_DANGEROUS_EXTENSIONS_HELP + classes: fancy + validate: + type: commalist + + + sanitize_svg: + type: toggle + label: PLUGIN_ADMIN.SANITIZE_SVG + help: PLUGIN_ADMIN.SANITIZE_SVG_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + default: true + validate: + type: bool diff --git a/system/blueprints/config/site.yaml b/system/blueprints/config/site.yaml new file mode 100644 index 0000000..6603823 --- /dev/null +++ b/system/blueprints/config/site.yaml @@ -0,0 +1,124 @@ +title: PLUGIN_ADMIN.SITE +form: + validation: loose + fields: + + content: + type: section + title: PLUGIN_ADMIN.DEFAULTS + underline: true + + fields: + title: + type: text + label: PLUGIN_ADMIN.SITE_TITLE + size: large + placeholder: PLUGIN_ADMIN.SITE_TITLE_PLACEHOLDER + help: PLUGIN_ADMIN.SITE_TITLE_HELP + + default_lang: + type: text + label: PLUGIN_ADMIN.SITE_DEFAULT_LANG + size: x-small + placeholder: PLUGIN_ADMIN.SITE_DEFAULT_LANG_PLACEHOLDER + help: PLUGIN_ADMIN.SITE_DEFAULT_LANG_HELP + + author.name: + type: text + size: large + label: PLUGIN_ADMIN.DEFAULT_AUTHOR + help: PLUGIN_ADMIN.DEFAULT_AUTHOR_HELP + + author.email: + type: text + size: large + label: PLUGIN_ADMIN.DEFAULT_EMAIL + help: PLUGIN_ADMIN.DEFAULT_EMAIL_HELP + validate: + type: email + + taxonomies: + type: selectize + size: large + label: PLUGIN_ADMIN.TAXONOMY_TYPES + classes: fancy + help: PLUGIN_ADMIN.TAXONOMY_TYPES_HELP + validate: + type: commalist + + summary: + type: section + title: PLUGIN_ADMIN.PAGE_SUMMARY + underline: true + + fields: + summary.enabled: + type: toggle + label: PLUGIN_ADMIN.ENABLED + highlight: 1 + help: PLUGIN_ADMIN.ENABLED_HELP + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + summary.size: + type: text + size: small + append: PLUGIN_ADMIN.CHARACTERS + label: PLUGIN_ADMIN.SUMMARY_SIZE + help: PLUGIN_ADMIN.SUMMARY_SIZE_HELP + validate: + type: int + min: 0 + max: 65536 + + summary.format: + type: toggle + label: PLUGIN_ADMIN.FORMAT + classes: fancy + help: PLUGIN_ADMIN.FORMAT_HELP + highlight: short + options: + 'short': PLUGIN_ADMIN.SHORT + 'long': PLUGIN_ADMIN.LONG + + summary.delimiter: + type: text + size: x-small + label: PLUGIN_ADMIN.DELIMITER + help: PLUGIN_ADMIN.DELIMITER_HELP + + metadata: + type: section + title: PLUGIN_ADMIN.METADATA + underline: true + + fields: + metadata: + type: array + label: PLUGIN_ADMIN.METADATA + help: PLUGIN_ADMIN.METADATA_HELP + placeholder_key: PLUGIN_ADMIN.METADATA_KEY + placeholder_value: PLUGIN_ADMIN.METADATA_VALUE + + routes: + type: section + title: PLUGIN_ADMIN.REDIRECTS_AND_ROUTES + underline: true + + fields: + redirects: + type: array + label: PLUGIN_ADMIN.CUSTOM_REDIRECTS + help: PLUGIN_ADMIN.CUSTOM_REDIRECTS_HELP + placeholder_key: PLUGIN_ADMIN.CUSTOM_REDIRECTS_PLACEHOLDER_KEY + placeholder_value: PLUGIN_ADMIN.CUSTOM_REDIRECTS_PLACEHOLDER_VALUE + + routes: + type: array + label: PLUGIN_ADMIN.CUSTOM_ROUTES + help: PLUGIN_ADMIN.CUSTOM_ROUTES_HELP + placeholder_key: PLUGIN_ADMIN.CUSTOM_ROUTES_PLACEHOLDER_KEY + placeholder_value: PLUGIN_ADMIN.CUSTOM_ROUTES_PLACEHOLDER_VALUE diff --git a/system/blueprints/config/streams.yaml b/system/blueprints/config/streams.yaml new file mode 100644 index 0000000..c73b80b --- /dev/null +++ b/system/blueprints/config/streams.yaml @@ -0,0 +1,8 @@ +title: PLUGIN_ADMIN.FILE_STREAMS + +form: + validation: loose + hidden: true + fields: + schemes.xxx: + type: array diff --git a/system/blueprints/config/system.yaml b/system/blueprints/config/system.yaml new file mode 100644 index 0000000..13b04d2 --- /dev/null +++ b/system/blueprints/config/system.yaml @@ -0,0 +1,1917 @@ +title: PLUGIN_ADMIN.SYSTEM + +form: + validation: loose + fields: + + system_tabs: + type: tabs + classes: side-tabs + + fields: + content: + type: tab + title: PLUGIN_ADMIN.CONTENT + + fields: + content_section: + type: section + title: PLUGIN_ADMIN.CONTENT + underline: true + + home.alias: + type: pages + size: large + classes: fancy + label: PLUGIN_ADMIN.HOME_PAGE + show_all: false + show_modular: false + show_root: false + show_slug: true + help: PLUGIN_ADMIN.HOME_PAGE_HELP + + home.hide_in_urls: + type: toggle + label: PLUGIN_ADMIN.HIDE_HOME_IN_URLS + help: PLUGIN_ADMIN.HIDE_HOME_IN_URLS_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + pages.theme: + type: themeselect + classes: fancy + selectize: true + size: medium + label: PLUGIN_ADMIN.DEFAULT_THEME + help: PLUGIN_ADMIN.DEFAULT_THEME_HELP + + pages.process: + type: checkboxes + label: PLUGIN_ADMIN.PROCESS + help: PLUGIN_ADMIN.PROCESS_HELP + default: [markdown: true, twig: true] + options: + markdown: Markdown + twig: Twig + use: keys + + pages.types: + type: array + label: PLUGIN_ADMIN.PAGE_TYPES + help: PLUGIN_ADMIN.PAGE_TYPES_HELP + size: small + default: ['html','htm','json','xml','txt','rss','atom'] + value_only: true + + timezone: + type: select + label: PLUGIN_ADMIN.TIMEZONE + size: medium + classes: fancy + help: PLUGIN_ADMIN.TIMEZONE_HELP + data-options@: '\Grav\Common\Utils::timezones' + default: '' + options: + '': 'Default (Server Timezone)' + + pages.dateformat.default: + type: select + size: medium + selectize: + create: true + label: PLUGIN_ADMIN.DEFAULT_DATE_FORMAT + help: PLUGIN_ADMIN.DEFAULT_DATE_FORMAT_HELP + placeholder: PLUGIN_ADMIN.DEFAULT_DATE_FORMAT_PLACEHOLDER + data-options@: '\Grav\Common\Utils::dateFormats' + validate: + type: string + + pages.dateformat.short: + type: dateformat + size: medium + classes: fancy + label: PLUGIN_ADMIN.SHORT_DATE_FORMAT + help: PLUGIN_ADMIN.SHORT_DATE_FORMAT_HELP + default: "jS M Y" + options: + "F jS \\a\\t g:ia": Date1 + "l jS \\of F g:i A": Date2 + "D, d M Y G:i:s": Date3 + "d-m-y G:i": Date4 + "jS M Y": Date5 + "Y-m-d G:i": Date6 + + pages.dateformat.long: + type: dateformat + size: medium + classes: fancy + label: PLUGIN_ADMIN.LONG_DATE_FORMAT + help: PLUGIN_ADMIN.LONG_DATE_FORMAT_HELP + options: + "F jS \\a\\t g:ia": Date1 + "l jS \\of F g:i A": Date2 + "D, d M Y G:i:s": Date3 + "d-m-y G:i": Date4 + "jS M Y": Date5 + "Y-m-d G:i:s": Date6 + + pages.order.by: + type: select + size: large + classes: fancy + label: PLUGIN_ADMIN.DEFAULT_ORDERING + help: PLUGIN_ADMIN.DEFAULT_ORDERING_HELP + options: + default: PLUGIN_ADMIN.DEFAULT_ORDERING_DEFAULT + folder: PLUGIN_ADMIN.DEFAULT_ORDERING_FOLDER + title: PLUGIN_ADMIN.DEFAULT_ORDERING_TITLE + date: PLUGIN_ADMIN.DEFAULT_ORDERING_DATE + + pages.order.dir: + type: toggle + label: PLUGIN_ADMIN.DEFAULT_ORDER_DIRECTION + highlight: asc + default: desc + help: PLUGIN_ADMIN.DEFAULT_ORDER_DIRECTION_HELP + options: + asc: PLUGIN_ADMIN.ASCENDING + desc: PLUGIN_ADMIN.DESCENDING + + pages.list.count: + type: text + size: x-small + append: PLUGIN_ADMIN.PAGES + label: PLUGIN_ADMIN.DEFAULT_PAGE_COUNT + help: PLUGIN_ADMIN.DEFAULT_PAGE_COUNT_HELP + validate: + type: number + min: 1 + + pages.publish_dates: + type: toggle + label: PLUGIN_ADMIN.DATE_BASED_PUBLISHING + help: PLUGIN_ADMIN.DATE_BASED_PUBLISHING_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + pages.events: + type: checkboxes + label: PLUGIN_ADMIN.EVENTS + help: PLUGIN_ADMIN.EVENTS_HELP + default: [page: true, twig: true] + options: + page: Page Events + twig: Twig Events + use: keys + + pages.append_url_extension: + type: text + size: x-small + placeholder: "e.g. .html" + label: PLUGIN_ADMIN.APPEND_URL_EXT + help: PLUGIN_ADMIN.APPEND_URL_EXT_HELP + + pages.redirect_default_code: + type: select + size: medium + classes: fancy + label: PLUGIN_ADMIN.REDIRECT_DEFAULT_CODE + help: PLUGIN_ADMIN.REDIRECT_DEFAULT_CODE_HELP + default: 302 + options: + 301: PLUGIN_ADMIN.REDIRECT_OPTION_301 + 302: PLUGIN_ADMIN.REDIRECT_OPTION_302 + 303: PLUGIN_ADMIN.REDIRECT_OPTION_303 + + pages.redirect_default_route: + type: select + size: medium + classes: fancy + label: PLUGIN_ADMIN.REDIRECT_DEFAULT_ROUTE + help: PLUGIN_ADMIN.REDIRECT_DEFAULT_ROUTE_HELP + default: 0 + options: + 0: PLUGIN_ADMIN.REDIRECT_OPTION_NO_REDIRECT + 1: PLUGIN_ADMIN.REDIRECT_OPTION_DEFAULT_REDIRECT + 301: PLUGIN_ADMIN.REDIRECT_OPTION_301 + 302: PLUGIN_ADMIN.REDIRECT_OPTION_302 + validate: + type: int + + pages.redirect_trailing_slash: + type: select + size: medium + classes: fancy + label: PLUGIN_ADMIN.REDIRECT_TRAILING_SLASH + help: PLUGIN_ADMIN.REDIRECT_TRAILING_SLASH_HELP + default: 1 + options: + 0: PLUGIN_ADMIN.REDIRECT_OPTION_NO_REDIRECT + 1: PLUGIN_ADMIN.REDIRECT_OPTION_DEFAULT_REDIRECT + 301: PLUGIN_ADMIN.REDIRECT_OPTION_301 + 302: PLUGIN_ADMIN.REDIRECT_OPTION_302 + validate: + type: int + + pages.ignore_hidden: + type: toggle + label: PLUGIN_ADMIN.IGNORE_HIDDEN + help: PLUGIN_ADMIN.IGNORE_HIDDEN_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + pages.ignore_files: + type: selectize + size: large + label: PLUGIN_ADMIN.IGNORE_FILES + help: PLUGIN_ADMIN.IGNORE_FILES_HELP + classes: fancy + validate: + type: commalist + + pages.ignore_folders: + type: selectize + size: large + label: PLUGIN_ADMIN.IGNORE_FOLDERS + help: PLUGIN_ADMIN.IGNORE_FOLDERS_HELP + classes: fancy + validate: + type: commalist + + pages.hide_empty_folders: + type: toggle + label: PLUGIN_ADMIN.HIDE_EMPTY_FOLDERS + help: PLUGIN_ADMIN.HIDE_EMPTY_FOLDERS_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + pages.url_taxonomy_filters: + type: toggle + label: PLUGIN_ADMIN.ALLOW_URL_TAXONOMY_FILTERS + help: PLUGIN_ADMIN.ALLOW_URL_TAXONOMY_FILTERS_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + pages.twig_first: + type: toggle + label: PLUGIN_ADMIN.TWIG_FIRST + help: PLUGIN_ADMIN.TWIG_FIRST_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + pages.never_cache_twig: + type: toggle + label: PLUGIN_ADMIN.NEVER_CACHE_TWIG + help: PLUGIN_ADMIN.NEVER_CACHE_TWIG_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + pages.frontmatter.process_twig: + type: toggle + label: PLUGIN_ADMIN.FRONTMATTER_PROCESS_TWIG + help: PLUGIN_ADMIN.FRONTMATTER_PROCESS_TWIG_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + pages.frontmatter.ignore_fields: + type: selectize + size: large + placeholder: "e.g. forms" + label: PLUGIN_ADMIN.FRONTMATTER_IGNORE_FIELDS + help: PLUGIN_ADMIN.FRONTMATTER_IGNORE_FIELDS_HELP + classes: fancy + validate: + type: commalist + + languages: + type: tab + title: PLUGIN_ADMIN.LANGUAGES + + fields: + languages-section: + type: section + title: PLUGIN_ADMIN.LANGUAGES + underline: true + + languages.supported: + type: selectize + size: large + placeholder: "e.g. en, fr" + label: PLUGIN_ADMIN.SUPPORTED + help: PLUGIN_ADMIN.SUPPORTED_HELP + classes: fancy + validate: + type: commalist + + languages.default_lang: + type: text + size: x-small + label: PLUGIN_ADMIN.DEFAULT_LANG + help: PLUGIN_ADMIN.DEFAULT_LANG_HELP + + languages.include_default_lang: + type: toggle + label: PLUGIN_ADMIN.INCLUDE_DEFAULT_LANG + help: PLUGIN_ADMIN.INCLUDE_DEFAULT_LANG_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + languages.include_default_lang_file_extension: + type: toggle + label: PLUGIN_ADMIN.INCLUDE_DEFAULT_LANG_FILE_EXTENSION + help: PLUGIN_ADMIN.INCLUDE_DEFAULT_LANG_HELP_FILE_EXTENSION + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + languages.content_fallback: + type: list + label: PLUGIN_ADMIN.CONTENT_LANGUAGE_FALLBACKS + help: PLUGIN_ADMIN.CONTENT_LANGUAGE_FALLBACKS_HELP + fields: + key: + type: key + label: PLUGIN_ADMIN.LANGUAGE + help: PLUGIN_ADMIN.CONTENT_FALLBACK_LANGUAGE_HELP + placeholder: fr-ca + value: + type: selectize + size: large + placeholder: "fr, en" + label: PLUGIN_ADMIN.CONTENT_LANGUAGE_FALLBACK + help: PLUGIN_ADMIN.CONTENT_LANGUAGE_FALLBACK_HELP + classes: fancy +# TODO: does not work. +# validate: +# type: commalist + + languages.pages_fallback_only: + type: toggle + label: PLUGIN_ADMIN.PAGES_FALLBACK_ONLY + help: PLUGIN_ADMIN.PAGES_FALLBACK_ONLY_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + languages.translations: + type: toggle + label: PLUGIN_ADMIN.LANGUAGE_TRANSLATIONS + help: PLUGIN_ADMIN.LANGUAGE_TRANSLATIONS_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + languages.translations_fallback: + type: toggle + label: PLUGIN_ADMIN.TRANSLATIONS_FALLBACK + help: PLUGIN_ADMIN.TRANSLATIONS_FALLBACK_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + languages.session_store_active: + type: toggle + label: PLUGIN_ADMIN.ACTIVE_LANGUAGE_IN_SESSION + help: PLUGIN_ADMIN.ACTIVE_LANGUAGE_IN_SESSION_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + languages.http_accept_language: + type: toggle + label: PLUGIN_ADMIN.HTTP_ACCEPT_LANGUAGE + help: PLUGIN_ADMIN.HTTP_ACCEPT_LANGUAGE_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + languages.override_locale: + type: toggle + label: PLUGIN_ADMIN.OVERRIDE_LOCALE + help: PLUGIN_ADMIN.OVERRIDE_LOCALE_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + languages.debug: + type: toggle + label: PLUGIN_ADMIN.LANGUAGE_DEBUG + help: PLUGIN_ADMIN.LANGUAGE_DEBUG_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + http_headers: + type: tab + title: PLUGIN_ADMIN.HTTP_HEADERS + + fields: + http_headers_section: + type: section + title: PLUGIN_ADMIN.HTTP_HEADERS + underline: true + + pages.expires: + type: text + size: x-small + append: GRAV.NICETIME.SECOND_PLURAL + label: PLUGIN_ADMIN.EXPIRES + help: PLUGIN_ADMIN.EXPIRES_HELP + validate: + type: number + min: 1 + pages.cache_control: + type: text + size: medium + label: PLUGIN_ADMIN.CACHE_CONTROL + help: PLUGIN_ADMIN.CACHE_CONTROL_HELP + placeholder: 'e.g. public, max-age=31536000' + pages.last_modified: + type: toggle + label: PLUGIN_ADMIN.LAST_MODIFIED + help: PLUGIN_ADMIN.LAST_MODIFIED_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + pages.etag: + type: toggle + label: PLUGIN_ADMIN.ETAG + help: PLUGIN_ADMIN.ETAG_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + pages.vary_accept_encoding: + type: toggle + label: PLUGIN_ADMIN.VARY_ACCEPT_ENCODING + help: PLUGIN_ADMIN.VARY_ACCEPT_ENCODING_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + markdown: + type: tab + title: PLUGIN_ADMIN.MARKDOWN + + fields: + markdow_section: + type: section + title: PLUGIN_ADMIN.MARKDOWN + underline: true + + pages.markdown.extra: + type: toggle + label: Markdown extra + help: PLUGIN_ADMIN.MARKDOWN_EXTRA_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + pages.markdown.auto_line_breaks: + type: toggle + label: PLUGIN_ADMIN.AUTO_LINE_BREAKS + help: PLUGIN_ADMIN.AUTO_LINE_BREAKS_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + pages.markdown.auto_url_links: + type: toggle + label: PLUGIN_ADMIN.AUTO_URL_LINKS + help: PLUGIN_ADMIN.AUTO_URL_LINKS_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + pages.markdown.escape_markup: + type: toggle + label: PLUGIN_ADMIN.ESCAPE_MARKUP + help: PLUGIN_ADMIN.ESCAPE_MARKUP_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + pages.markdown.valid_link_attributes: + type: selectize + size: large + label: PLUGIN_ADMIN.VALID_LINK_ATTRIBUTES + help: PLUGIN_ADMIN.VALID_LINK_ATTRIBUTES_HELP + placeholder: "rel, target, id, class, classes" + classes: fancy + validate: + type: commalist + + caching: + type: tab + title: PLUGIN_ADMIN.CACHING + + fields: + caching_section: + type: section + title: PLUGIN_ADMIN.CACHING + underline: true + + cache.enabled: + type: toggle + label: PLUGIN_ADMIN.CACHING + help: PLUGIN_ADMIN.CACHING_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + cache.check.method: + type: select + size: medium + classes: fancy + label: PLUGIN_ADMIN.CACHE_CHECK_METHOD + help: PLUGIN_ADMIN.CACHE_CHECK_METHOD_HELP + options: + file: Markdown + Yaml file timestamps + folder: Folder timestamps + hash: All files timestamps + none: No timestamp checking + + cache.driver: + type: select + size: small + classes: fancy + label: PLUGIN_ADMIN.CACHE_DRIVER + help: PLUGIN_ADMIN.CACHE_DRIVER_HELP + options: + auto: Auto detect + file: File + apc: APC + apcu: APCu + memcache: Memcache + memcached: Memcached + wincache: WinCache + redis: Redis + + cache.prefix: + type: text + size: x-small + label: PLUGIN_ADMIN.CACHE_PREFIX + help: PLUGIN_ADMIN.CACHE_PREFIX_HELP + placeholder: PLUGIN_ADMIN.CACHE_PREFIX_PLACEHOLDER + + cache.purge_max_age_days: + type: text + size: x-small + append: GRAV.NICETIME.DAY_PLURAL + label: PLUGIN_ADMIN.CACHE_PURGE_AGE + help: PLUGIN_ADMIN.CACHE_PURGE_AGE_HELP + validate: + type: number + min: 1 + max: 365 + step: 1 + default: 30 + + cache.purge_at: + type: cron + label: PLUGIN_ADMIN.CACHE_PURGE_JOB + help: PLUGIN_ADMIN.CACHE_PURGE_JOB_HELP + default: '* 4 * * *' + + cache.clear_at: + type: cron + label: PLUGIN_ADMIN.CACHE_CLEAR_JOB + help: PLUGIN_ADMIN.CACHE_CLEAR_JOB_HELP + default: '* 3 * * *' + + cache.clear_job_type: + type: select + size: medium + label: PLUGIN_ADMIN.CACHE_JOB_TYPE + help: PLUGIN_ADMIN.CACHE_JOB_TYPE_HELP + options: + standard: Standard Cache Folders + all: All Cache Folders + + cache.clear_images_by_default: + type: toggle + label: PLUGIN_ADMIN.CLEAR_IMAGES_BY_DEFAULT + help: PLUGIN_ADMIN.CLEAR_IMAGES_BY_DEFAULT_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + cache.cli_compatibility: + type: toggle + label: PLUGIN_ADMIN.CLI_COMPATIBILITY + help: PLUGIN_ADMIN.CLI_COMPATIBILITY_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + cache.lifetime: + type: text + size: small + append: GRAV.NICETIME.SECOND_PLURAL + label: PLUGIN_ADMIN.LIFETIME + help: PLUGIN_ADMIN.LIFETIME_HELP + validate: + type: number + + cache.gzip: + type: toggle + label: PLUGIN_ADMIN.GZIP_COMPRESSION + help: PLUGIN_ADMIN.GZIP_COMPRESSION_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + cache.allow_webserver_gzip: + type: toggle + label: PLUGIN_ADMIN.ALLOW_WEBSERVER_GZIP + help: PLUGIN_ADMIN.ALLOW_WEBSERVER_GZIP_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + cache.memcache.server: + type: text + size: medium + label: PLUGIN_ADMIN.MEMCACHE_SERVER + help: PLUGIN_ADMIN.MEMCACHE_SERVER_HELP + placeholder: "localhost" + + cache.memcache.port: + type: text + size: small + label: PLUGIN_ADMIN.MEMCACHE_PORT + help: PLUGIN_ADMIN.MEMCACHE_PORT_HELP + placeholder: "11211" + + cache.memcached.server: + type: text + size: medium + label: PLUGIN_ADMIN.MEMCACHED_SERVER + help: PLUGIN_ADMIN.MEMCACHED_SERVER_HELP + placeholder: "localhost" + + cache.memcached.port: + type: text + size: small + label: PLUGIN_ADMIN.MEMCACHED_PORT + help: PLUGIN_ADMIN.MEMCACHED_PORT_HELP + placeholder: "11211" + + cache.redis.socket: + type: text + size: medium + label: PLUGIN_ADMIN.REDIS_SOCKET + help: PLUGIN_ADMIN.REDIS_SOCKET_HELP + placeholder: "/var/run/redis/redis.sock" + + cache.redis.server: + type: text + size: medium + label: PLUGIN_ADMIN.REDIS_SERVER + help: PLUGIN_ADMIN.REDIS_SERVER_HELP + placeholder: "localhost" + + cache.redis.port: + type: text + size: small + label: PLUGIN_ADMIN.REDIS_PORT + help: PLUGIN_ADMIN.REDIS_PORT_HELP + placeholder: "6379" + + cache.redis.password: + type: text + size: small + label: PLUGIN_ADMIN.REDIS_PASSWORD + + cache.redis.database: + type: text + size: medium + label: PLUGIN_ADMIN.REDIS_DATABASE + help: PLUGIN_ADMIN.REDIS_DATABASE_HELP + placeholder: "0" + validate: + type: number + min: 0 + + flex_caching: + type: section + title: PLUGIN_ADMIN.FLEX_CACHING + + flex.cache.index.enabled: + type: toggle + label: PLUGIN_ADMIN.FLEX_INDEX_CACHE_ENABLED + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + flex.cache.index.lifetime: + type: text + label: PLUGIN_ADMIN.FLEX_INDEX_CACHE_LIFETIME + default: 60 + validate: + type: int + + flex.cache.object.enabled: + type: toggle + label: PLUGIN_ADMIN.FLEX_OBJECT_CACHE_ENABLED + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + flex.cache.object.lifetime: + type: text + label: PLUGIN_ADMIN.FLEX_OBJECT_CACHE_LIFETIME + default: 600 + validate: + type: int + + flex.cache.render.enabled: + type: toggle + label: PLUGIN_ADMIN.FLEX_RENDER_CACHE_ENABLED + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + flex.cache.render.lifetime: + type: text + label: PLUGIN_ADMIN.FLEX_RENDER_CACHE_LIFETIME + default: 600 + validate: + type: int + + twig: + type: tab + title: PLUGIN_ADMIN.TWIG_TEMPLATING + + fields: + twig_section: + type: section + title: PLUGIN_ADMIN.TWIG_TEMPLATING + underline: true + + twig.cache: + type: toggle + label: PLUGIN_ADMIN.TWIG_CACHING + help: PLUGIN_ADMIN.TWIG_CACHING_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + twig.debug: + type: toggle + label: PLUGIN_ADMIN.TWIG_DEBUG + help: PLUGIN_ADMIN.TWIG_DEBUG_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + twig.auto_reload: + type: toggle + label: PLUGIN_ADMIN.DETECT_CHANGES + help: PLUGIN_ADMIN.DETECT_CHANGES_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + twig.autoescape: + type: toggle + label: PLUGIN_ADMIN.AUTOESCAPE_VARIABLES + help: PLUGIN_ADMIN.AUTOESCAPE_VARIABLES_HELP + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + twig.umask_fix: + type: toggle + label: PLUGIN_ADMIN.TWIG_UMASK_FIX + help: PLUGIN_ADMIN.TWIG_UMASK_FIX_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + assets: + type: tab + title: PLUGIN_ADMIN.ASSETS + + fields: + general_config_section: + type: section + title: PLUGIN_ADMIN.GENERAL_CONFIG + underline: true + + assets.enable_asset_timestamp: + type: toggle + label: PLUGIN_ADMIN.ENABLED_TIMESTAMPS_ON_ASSETS + help: PLUGIN_ADMIN.ENABLED_TIMESTAMPS_ON_ASSETS_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + assets.enable_asset_sri: + type: toggle + label: PLUGIN_ADMIN.ENABLED_SRI_ON_ASSETS + help: PLUGIN_ADMIN.ENABLED_SRI_ON_ASSETS_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + assets.collections: + type: multilevel + label: PLUGIN_ADMIN.COLLECTIONS + placeholder_key: collection_name + placeholder_value: collection_path + validate: + type: array + + + css_assets_section: + type: section + title: PLUGIN_ADMIN.CSS_ASSETS + underline: true + + assets.css_pipeline: + type: toggle + label: PLUGIN_ADMIN.CSS_PIPELINE + help: PLUGIN_ADMIN.CSS_PIPELINE_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + assets.css_pipeline_include_externals: + type: toggle + label: PLUGIN_ADMIN.CSS_PIPELINE_INCLUDE_EXTERNALS + help: PLUGIN_ADMIN.CSS_PIPELINE_INCLUDE_EXTERNALS_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + assets.css_pipeline_before_excludes: + type: toggle + label: PLUGIN_ADMIN.CSS_PIPELINE_BEFORE_EXCLUDES + help: PLUGIN_ADMIN.CSS_PIPELINE_BEFORE_EXCLUDES_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + assets.css_minify: + type: toggle + label: PLUGIN_ADMIN.CSS_MINIFY + help: PLUGIN_ADMIN.CSS_MINIFY_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + assets.css_minify_windows: + type: toggle + label: PLUGIN_ADMIN.CSS_MINIFY_WINDOWS_OVERRIDE + help: PLUGIN_ADMIN.CSS_MINIFY_WINDOWS_OVERRIDE_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + assets.css_rewrite: + type: toggle + label: PLUGIN_ADMIN.CSS_REWRITE + help: PLUGIN_ADMIN.CSS_REWRITE_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + js_assets_section: + type: section + title: PLUGIN_ADMIN.JS_ASSETS + underline: true + + assets.js_pipeline: + type: toggle + label: PLUGIN_ADMIN.JAVASCRIPT_PIPELINE + help: PLUGIN_ADMIN.JAVASCRIPT_PIPELINE_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + assets.js_pipeline_include_externals: + type: toggle + label: PLUGIN_ADMIN.JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS + help: PLUGIN_ADMIN.JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + assets.js_pipeline_before_excludes: + type: toggle + label: PLUGIN_ADMIN.JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES + help: PLUGIN_ADMIN.JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + assets.js_minify: + type: toggle + label: PLUGIN_ADMIN.JAVASCRIPT_MINIFY + help: PLUGIN_ADMIN.JAVASCRIPT_MINIFY_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + js_module_assets_section: + type: section + title: PLUGIN_ADMIN.JS_MODULE_ASSETS + underline: true + + assets.js_module_pipeline: + type: toggle + label: PLUGIN_ADMIN.JAVASCRIPT_MODULE_PIPELINE + help: PLUGIN_ADMIN.JAVASCRIPT_MODULE_PIPELINE_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + assets.js_module_pipeline_include_externals: + type: toggle + label: PLUGIN_ADMIN.JAVASCRIPT_MODULE_PIPELINE_INCLUDE_EXTERNALS + help: PLUGIN_ADMIN.JAVASCRIPT_MODULE_PIPELINE_INCLUDE_EXTERNALS_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + assets.js_module_pipeline_before_excludes: + type: toggle + label: PLUGIN_ADMIN.JAVASCRIPT_MODULE_PIPELINE_BEFORE_EXCLUDES + help: PLUGIN_ADMIN.JAVASCRIPT_MODULE_PIPELINE_BEFORE_EXCLUDES_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + + + errors: + type: tab + title: PLUGIN_ADMIN.ERROR_HANDLER + + fields: + errors_section: + type: section + title: PLUGIN_ADMIN.ERROR_HANDLER + underline: true + + errors.display: + type: select + label: PLUGIN_ADMIN.DISPLAY_ERRORS + help: PLUGIN_ADMIN.DISPLAY_ERRORS_HELP + size: medium + highlight: 1 + options: + -1: PLUGIN_ADMIN.ERROR_SYSTEM + 0: PLUGIN_ADMIN.ERROR_SIMPLE + 1: PLUGIN_ADMIN.ERROR_FULL_BACKTRACE + validate: + type: int + + + errors.log: + type: toggle + label: PLUGIN_ADMIN.LOG_ERRORS + help: PLUGIN_ADMIN.LOG_ERRORS_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + log.handler: + type: select + size: small + label: PLUGIN_ADMIN.LOG_HANDLER + help: PLUGIN_ADMIN.LOG_HANDLER_HELP + default: 'file' + options: + 'file': 'File' + 'syslog': 'Syslog' + + log.syslog.facility: + type: select + size: small + label: PLUGIN_ADMIN.SYSLOG_FACILITY + help: PLUGIN_ADMIN.SYSLOG_FACILITY_HELP + default: local6 + options: + auth: auth + authpriv: authpriv + cron: cron + daemon: daemon + kern: kern + lpr: lpr + mail: mail + news: news + syslog: syslog + user: user + uucp: uucp + local0: local0 + local1: local1 + local2: local2 + local3: local3 + local4: local4 + local5: local5 + local6: local6 + local7: local7 + + log.syslog.tag: + type: text + size: small + label: PLUGIN_ADMIN.SYSLOG_TAG + help: PLUGIN_ADMIN.SYSLOG_TAG_HELP + placeholder: "grav" + + debugger: + type: tab + title: PLUGIN_ADMIN.DEBUGGER + + fields: + debugger_section: + type: section + title: PLUGIN_ADMIN.DEBUGGER + underline: true + + debugger.enabled: + type: toggle + label: PLUGIN_ADMIN.DEBUGGER + help: PLUGIN_ADMIN.DEBUGGER_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + debugger.provider: + type: select + label: PLUGIN_ADMIN.DEBUGGER_PROVIDER + help: PLUGIN_ADMIN.DEBUGGER_PROVIDER_HELP + size: medium + default: debugbar + options: + debugbar: PLUGIN_ADMIN.DEBUGGER_DEBUGBAR + clockwork: PLUGIN_ADMIN.DEBUGGER_CLOCKWORK + + debugger.censored: + type: toggle + label: PLUGIN_ADMIN.DEBUGGER_CENSORED + help: PLUGIN_ADMIN.DEBUGGER_CENSORED_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + debugger.shutdown.close_connection: + type: toggle + label: PLUGIN_ADMIN.SHUTDOWN_CLOSE_CONNECTION + help: PLUGIN_ADMIN.SHUTDOWN_CLOSE_CONNECTION_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + media: + type: tab + title: PLUGIN_ADMIN.MEDIA + + fields: + media_section: + type: section + title: PLUGIN_ADMIN.MEDIA + underline: true + + images.adapter: + type: select + size: small + label: PLUGIN_ADMIN.IMAGE_ADAPTER + help: PLUGIN_ADMIN.IMAGE_ADAPTER_HELP + highlight: gd + options: + gd: GD (PHP built-in) + imagick: Imagick + + images.default_image_quality: + type: range + append: '%' + label: PLUGIN_ADMIN.DEFAULT_IMAGE_QUALITY + help: PLUGIN_ADMIN.DEFAULT_IMAGE_QUALITY_HELP + validate: + min: 1 + max: 100 + + images.cache_all: + type: toggle + label: PLUGIN_ADMIN.CACHE_ALL + help: PLUGIN_ADMIN.CACHE_ALL_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + images.cache_perms: + type: select + size: small + label: PLUGIN_ADMIN.CACHE_PERMS + help: PLUGIN_ADMIN.CACHE_PERMS_HELP + highlight: '0755' + options: + '0755': '0755' + '0775': '0775' + + images.debug: + type: toggle + label: PLUGIN_ADMIN.IMAGES_DEBUG + help: PLUGIN_ADMIN.IMAGES_DEBUG_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + images.auto_fix_orientation: + type: toggle + label: PLUGIN_ADMIN.IMAGES_AUTO_FIX_ORIENTATION + help: PLUGIN_ADMIN.IMAGES_AUTO_FIX_ORIENTATION_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + images.defaults.loading: + type: select + size: small + label: PLUGIN_ADMIN.IMAGES_LOADING + help: PLUGIN_ADMIN.IMAGES_LOADING_HELP + highlight: auto + options: + auto: Auto + lazy: Lazy + eager: Eager + + images.defaults.decoding: + type: select + size: small + label: PLUGIN_ADMIN.IMAGES_DECODING + help: PLUGIN_ADMIN.IMAGES_DECODING_HELP + highlight: auto + options: + auto: Auto + sync: Sync + async: Async + + images.defaults.fetchpriority: + type: select + size: small + label: PLUGIN_ADMIN.IMAGES_FETCHPRIORITY + help: PLUGIN_ADMIN.IMAGES_FETCHPRIORITY_HELP + highlight: auto + options: + auto: Auto + high: High + low: Low + + images.seofriendly: + type: toggle + label: PLUGIN_ADMIN.IMAGES_SEOFRIENDLY + help: PLUGIN_ADMIN.IMAGES_SEOFRIENDLY_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + media.enable_media_timestamp: + type: toggle + label: PLUGIN_ADMIN.ENABLE_MEDIA_TIMESTAMP + help: PLUGIN_ADMIN.ENABLE_MEDIA_TIMESTAMP_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + media.auto_metadata_exif: + type: toggle + label: PLUGIN_ADMIN.ENABLE_AUTO_METADATA + help: PLUGIN_ADMIN.ENABLE_AUTO_METADATA_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + media.allowed_fallback_types: + type: selectize + size: large + label: PLUGIN_ADMIN.FALLBACK_TYPES + help: PLUGIN_ADMIN.FALLBACK_TYPES_HELP + classes: fancy + validate: + type: commalist + + media.unsupported_inline_types: + type: selectize + size: large + label: PLUGIN_ADMIN.INLINE_TYPES + help: PLUGIN_ADMIN.INLINE_TYPES_HELP + classes: fancy + validate: + type: commalist + + section_images_cls: + type: section + title: PLUGIN_ADMIN.IMAGES_CLS_TITLE + underline: true + + images.cls.auto_sizes: + type: toggle + label: PLUGIN_ADMIN.IMAGES_CLS_AUTO_SIZES + help: PLUGIN_ADMIN.IMAGES_CLS_AUTO_SIZES_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + images.cls.aspect_ratio: + type: toggle + label: PLUGIN_ADMIN.IMAGES_CLS_ASPECT_RATIO + help: PLUGIN_ADMIN.IMAGES_CLS_ASPECT_RATIO_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + images.cls.retina_scale: + type: select + label: PLUGIN_ADMIN.IMAGES_CLS_RETINA_SCALE + help: PLUGIN_ADMIN.IMAGES_CLS_RETINA_SCALE_HELP + size: small + highlight: 1 + options: + 1: 1X + 2: 2X + 3: 3X + 4: 4X + + session: + type: tab + title: PLUGIN_ADMIN.SESSION + + fields: + session_section: + type: section + title: PLUGIN_ADMIN.SESSION + underline: true + + session.enabled: + type: hidden + label: PLUGIN_ADMIN.ENABLED + help: PLUGIN_ADMIN.SESSION_ENABLED_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + default: true + validate: + type: bool + + session.initialize: + type: toggle + label: PLUGIN_ADMIN.SESSION_INITIALIZE + help: PLUGIN_ADMIN.SESSION_INITIALIZE_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + default: true + validate: + type: bool + + session.timeout: + type: text + size: small + append: GRAV.NICETIME.SECOND_PLURAL + label: PLUGIN_ADMIN.TIMEOUT + help: PLUGIN_ADMIN.TIMEOUT_HELP + validate: + type: number + min: 0 + + session.name: + type: text + size: small + label: PLUGIN_ADMIN.NAME + help: PLUGIN_ADMIN.SESSION_NAME_HELP + + session.uniqueness: + type: select + size: medium + label: PLUGIN_ADMIN.SESSION_UNIQUENESS + help: PLUGIN_ADMIN.SESSION_UNIQUENESS_HELP + highlight: path + default: path + options: + path: Grav's root file path + salt: Grav's random security salt + + session.secure: + type: toggle + label: PLUGIN_ADMIN.SESSION_SECURE + help: PLUGIN_ADMIN.SESSION_SECURE_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + default: false + validate: + type: bool + + session.secure_https: + type: toggle + label: PLUGIN_ADMIN.SESSION_SECURE_HTTPS + help: PLUGIN_ADMIN.SESSION_SECURE_HTTPS_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + default: true + validate: + type: bool + + session.httponly: + type: toggle + label: PLUGIN_ADMIN.SESSION_HTTPONLY + help: PLUGIN_ADMIN.SESSION_HTTPONLY_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + default: true + validate: + type: bool + + session.domain: + type: text + size: small + label: PLUGIN_ADMIN.SESSION_DOMAIN + help: PLUGIN_ADMIN.SESSION_DOMAIN_HELP + + session.path: + type: text + size: small + label: PLUGIN_ADMIN.SESSION_PATH + help: PLUGIN_ADMIN.SESSION_PATH_HELP + + session.samesite: + type: text + size: small + label: PLUGIN_ADMIN.SESSION_SAMESITE + help: PLUGIN_ADMIN.SESSION_SAMESITE_HELP + + session.split: + type: toggle + label: PLUGIN_ADMIN.SESSION_SPLIT + help: PLUGIN_ADMIN.SESSION_SPLIT_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + default: true + validate: + type: bool + + advanced: + type: tab + title: PLUGIN_ADMIN.ADVANCED + + fields: + advanced_section: + type: section + title: PLUGIN_ADMIN.ADVANCED + underline: true + + gpm_section: + type: section + title: PLUGIN_ADMIN.GPM_SECTION + + gpm.releases: + type: toggle + label: PLUGIN_ADMIN.GPM_RELEASES + highlight: stable + help: PLUGIN_ADMIN.GPM_RELEASES_HELP + options: + stable: PLUGIN_ADMIN.STABLE + testing: PLUGIN_ADMIN.TESTING + + gpm.official_gpm_only: + type: toggle + label: PLUGIN_ADMIN.GPM_OFFICIAL_ONLY + highlight: 1 + help: PLUGIN_ADMIN.GPM_OFFICIAL_ONLY_HELP + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + default: true + validate: + type: bool + + http_section: + type: section + title: PLUGIN_ADMIN.HTTP_SECTION + + http.method: + type: toggle + label: PLUGIN_ADMIN.GPM_METHOD + highlight: auto + help: PLUGIN_ADMIN.GPM_METHOD_HELP + options: + auto: PLUGIN_ADMIN.AUTO + fopen: PLUGIN_ADMIN.FOPEN + curl: PLUGIN_ADMIN.CURL + + http.enable_proxy: + type: toggle + label: PLUGIN_ADMIN.SSL_ENABLE_PROXY + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + default: false + validate: + type: bool + + http.proxy_url: + type: text + size: medium + placeholder: "e.g. 127.0.0.1:3128" + label: PLUGIN_ADMIN.PROXY_URL + help: PLUGIN_ADMIN.PROXY_URL_HELP + + http.proxy_cert_path: + type: text + size: medium + placeholder: "e.g. /Users/bob/certs/" + label: PLUGIN_ADMIN.PROXY_CERT + help: PLUGIN_ADMIN.PROXY_CERT_HELP + + http.verify_peer: + type: toggle + label: PLUGIN_ADMIN.SSL_VERIFY_PEER + highlight: 1 + help: PLUGIN_ADMIN.SSL_VERIFY_PEER_HELP + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + http.verify_host: + type: toggle + label: PLUGIN_ADMIN.SSL_VERIFY_HOST + highlight: 1 + help: PLUGIN_ADMIN.SSL_VERIFY_HOST_HELP + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + http.concurrent_connections: + type: number + size: x-small + label: PLUGIN_ADMIN.HTTP_CONNECTIONS + help: PLUGIN_ADMIN.HTTP_CONNECTIONS_HELP + validate: + min: 1 + max: 20 + + misc_section: + type: section + title: PLUGIN_ADMIN.MISC_SECTION + + reverse_proxy_setup: + type: toggle + label: PLUGIN_ADMIN.REVERSE_PROXY + highlight: 0 + help: PLUGIN_ADMIN.REVERSE_PROXY_HELP + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + username_regex: + type: text + size: large + label: PLUGIN_ADMIN.USERNAME_REGEX + help: PLUGIN_ADMIN.USERNAME_REGEX_HELP + + pwd_regex: + type: text + size: large + label: PLUGIN_ADMIN.PWD_REGEX + help: PLUGIN_ADMIN.PWD_REGEX_HELP + + intl_enabled: + type: toggle + label: PLUGIN_ADMIN.INTL_ENABLED + highlight: 1 + help: PLUGIN_ADMIN.INTL_ENABLED_HELP + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + wrapped_site: + type: toggle + label: PLUGIN_ADMIN.WRAPPED_SITE + highlight: 0 + help: PLUGIN_ADMIN.WRAPPED_SITE_HELP + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + absolute_urls: + type: toggle + label: PLUGIN_ADMIN.ABSOLUTE_URLS + highlight: 0 + help: PLUGIN_ADMIN.ABSOLUTE_URLS_HELP + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + param_sep: + type: select + size: medium + label: PLUGIN_ADMIN.PARAMETER_SEPARATOR + classes: fancy + help: PLUGIN_ADMIN.PARAMETER_SEPARATOR_HELP + default: '' + options: + ':': ': (default)' + ';': '; (for Apache running on Windows)' + + force_ssl: + type: toggle + label: PLUGIN_ADMIN.FORCE_SSL + highlight: 0 + help: PLUGIN_ADMIN.FORCE_SSL_HELP + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + force_lowercase_urls: + type: toggle + label: PLUGIN_ADMIN.FORCE_LOWERCASE_URLS + highlight: 1 + default: 1 + help: PLUGIN_ADMIN.FORCE_LOWERCASE_URLS_HELP + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + custom_base_url: + type: text + size: medium + placeholder: "e.g. http://yoursite.com/yourpath" + label: PLUGIN_ADMIN.CUSTOM_BASE_URL + help: PLUGIN_ADMIN.CUSTOM_BASE_URL_HELP + + http_x_forwarded.protocol: + type: toggle + label: HTTP_X_FORWARDED_PROTO Enabled + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + http_x_forwarded.host: + type: toggle + label: HTTP_X_FORWARDED_HOST Enabled + highlight: 0 + default: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + http_x_forwarded.port: + type: toggle + label: HTTP_X_FORWARDED_PORT Enabled + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + http_x_forwarded.ip: + type: toggle + label: HTTP_X_FORWARDED IP Enabled + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + + strict_mode.blueprint_compat: + type: toggle + label: PLUGIN_ADMIN.STRICT_BLUEPRINT_COMPAT + highlight: 0 + default: 0 + help: PLUGIN_ADMIN.STRICT_BLUEPRINT_COMPAT_HELP + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + strict_mode.yaml_compat: + type: toggle + label: PLUGIN_ADMIN.STRICT_YAML_COMPAT + highlight: 0 + default: 0 + help: PLUGIN_ADMIN.STRICT_YAML_COMPAT_HELP + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + strict_mode.twig_compat: + type: toggle + label: PLUGIN_ADMIN.STRICT_TWIG_COMPAT + highlight: 0 + default: 0 + help: PLUGIN_ADMIN.STRICT_TWIG_COMPAT_HELP + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + + accounts: + type: tab + title: PLUGIN_ADMIN.ACCOUNTS + + fields: + flex_accounts: + type: section + title: User Accounts + + accounts.type: + type: select + label: PLUGIN_ADMIN.ACCOUNTS_TYPE + highlight: stable + help: PLUGIN_ADMIN.ACCOUNTS_TYPE_HELP + options: + regular: PLUGIN_ADMIN.REGULAR + flex: PLUGIN_ADMIN.FLEX + + accounts.storage: + type: select + label: PLUGIN_ADMIN.ACCOUNTS_STORAGE + highlight: stable + help: PLUGIN_ADMIN.ACCOUNTS_STORAGE_HELP + options: + file: PLUGIN_ADMIN.FILE + folder: PLUGIN_ADMIN.FOLDER + + accounts.avatar: + type: select + label: PLUGIN_ADMIN.AVATAR + default: gravatar + help: PLUGIN_ADMIN.AVATAR_HELP + options: + multiavatar: Multiavatar [local] + gravatar: Gravatar [external] + +# experimental: +# type: tab +# title: PLUGIN_ADMIN.EXPERIMENTAL +# +# fields: +# experimental_section: +# type: section +# title: PLUGIN_ADMIN.EXPERIMENTAL +# underline: true +# +# flex_pages: +# type: section +# title: Flex Pages +# +# pages.type: +# type: select +# label: PLUGIN_ADMIN.PAGES_TYPE +# highlight: regular +# help: PLUGIN_ADMIN.PAGES_TYPE_HELP +# options: +# regular: PLUGIN_ADMIN.REGULAR +# flex: PLUGIN_ADMIN.FLEX +# +# pages.type: +# type: hidden + + + diff --git a/system/blueprints/flex/accounts.yaml b/system/blueprints/flex/accounts.yaml new file mode 100644 index 0000000..52eff69 --- /dev/null +++ b/system/blueprints/flex/accounts.yaml @@ -0,0 +1,8 @@ +title: Flex User Accounts +description: Manage your User Accounts in Flex. +type: flex-objects + +# Deprecated in Grav 1.7.0-rc.4: file was renamed to user-accounts.yaml +extends@: + type: user-accounts + context: blueprints://flex diff --git a/system/blueprints/flex/configure/compat.yaml b/system/blueprints/flex/configure/compat.yaml new file mode 100644 index 0000000..08497d4 --- /dev/null +++ b/system/blueprints/flex/configure/compat.yaml @@ -0,0 +1,17 @@ +form: + compatibility: + type: tab + title: Compatibility + fields: + object.compat.events: + type: toggle + toggleable: true + label: Admin event compatibility + help: Enables onAdminSave and onAdminAfterSave events for plugins + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool diff --git a/system/blueprints/flex/pages.yaml b/system/blueprints/flex/pages.yaml new file mode 100644 index 0000000..89dab6a --- /dev/null +++ b/system/blueprints/flex/pages.yaml @@ -0,0 +1,212 @@ +title: Pages +description: Manage your Grav Pages in Flex. +type: flex-objects + +# Extends a page (blueprint gets overridden inside the object) +extends@: + type: default + context: blueprints://pages + +# +# HIGHLY SPECIALIZED FLEX TYPE, AVOID USING PAGES AS BASE FOR YOUR OWN TYPE. +# + +# Flex configuration +config: + # Administration Configuration (needs Flex Objects plugin) + admin: + # Admin router + router: + path: '/pages' + + # Permissions + permissions: + # Primary permissions + admin.pages: + type: crudl + label: Pages + admin.configuration.pages: + type: default + label: Pages Configuration + + # Admin menu + menu: + list: + route: '/pages' + title: PLUGIN_ADMIN.PAGES + icon: fa-file-text + authorize: ['admin.pages.list', 'admin.super'] + priority: 5 + + # Admin template type (folder) + template: pages + + # Allowed admin actions + actions: + list: true + create: true + read: true + update: true + delete: true + + # List view + list: + # Fields shown in the list view + fields: + published: + width: 8 + alias: header.published + visible: + width: 8 + field: + label: Visible + type: toggle + menu: + link: edit + alias: header.menu + full_route: + field: + label: Route + type: text + link: edit + sort: + field: key + name: + width: 8 + field: + label: Type + type: text + translations: + width: 8 + field: + label: Translations + type: text +# updated_date: +# alias: header.update_date + + # Extra options + options: + # Default number of records for pagination + per_page: 20 + # Default ordering + order: + by: key + dir: asc + + # TODO: not used yet + buttons: + back: + icon: reply + title: PLUGIN_ADMIN.BACK + add: + icon: plus + label: PLUGIN_ADMIN.ADD + + edit: + title: + template: "{% if object.root %}Root ( <root> ){% else %}{{ (form.value('header.title') ?? form.value('folder'))|e }} ( {{ (object.getRoute().toString(false) ?: '/')|e }} ){% endif %}" + + # TODO: not used yet + buttons: + back: + icon: reply + title: PLUGIN_ADMIN.BACK + preview: + icon: eye + title: PLUGIN_ADMIN.PREVIEW + add: + icon: plus + label: PLUGIN_ADMIN.ADD + copy: + icon: copy + label: PLUGIN_ADMIN.COPY + move: + icon: arrows + label: PLUGIN_ADMIN.MOVE + delete: + icon: close + label: PLUGIN_ADMIN.DELETE + save: + icon: check + label: PLUGIN_ADMIN.SAVE + + # Preview View + preview: + enabled: true + + # Configure view + configure: + authorize: 'admin.configuration.pages' + + # Site Configuration + site: + # Hide from flex types + hidden: true + templates: + collection: + # Lookup for the template layout files for collections of objects + paths: + - 'flex/{TYPE}/collection/{LAYOUT}{EXT}' + object: + # Lookup for the template layout files for objects + paths: + - 'flex/{TYPE}/object/{LAYOUT}{EXT}' + defaults: + # Default template {TYPE}; overridden by filename of this blueprint if template folder exists + type: pages + # Default template {LAYOUT}; can be overridden in render calls (usually Twig in templates) + layout: default + + # Default filters for frontend. + filter: + - withPublished + + # Data Configuration + data: + object: 'Grav\Common\Flex\Types\Pages\PageObject' + collection: 'Grav\Common\Flex\Types\Pages\PageCollection' + index: 'Grav\Common\Flex\Types\Pages\PageIndex' + storage: + class: 'Grav\Common\Flex\Types\Pages\Storage\PageStorage' + options: + formatter: + class: 'Grav\Framework\File\Formatter\MarkdownFormatter' + folder: 'page://' + # Keep index file in filesystem to speed up lookups + indexed: true + # Set default ordering of the pages + ordering: + storage_key: ASC + search: + # Search options + options: + contains: 1 + # Fields to be searched + fields: + - key + - slug + - menu + - title + +blueprints: + configure: + fields: + import@: + type: configure/compat + context: blueprints://flex + +# Regular form definition +form: + fields: + lang: + type: hidden + value: '' + + tabs: + fields: + security: + type: tab + title: PLUGIN_ADMIN.SECURITY + import@: + type: partials/security + context: blueprints://pages diff --git a/system/blueprints/flex/shared/configure.yaml b/system/blueprints/flex/shared/configure.yaml new file mode 100644 index 0000000..e3e5dcb --- /dev/null +++ b/system/blueprints/flex/shared/configure.yaml @@ -0,0 +1,70 @@ +form: + validation: loose + + fields: + tabs: + type: tabs + fields: + cache: + type: tab + title: Caching + fields: + object.cache.index.enabled: + type: toggle + toggleable: true + label: PLUGIN_ADMIN.FLEX_INDEX_CACHE_ENABLED + highlight: 1 + config-default@: system.flex.cache.index.enabled + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + object.cache.index.lifetime: + type: text + toggleable: true + label: PLUGIN_ADMIN.FLEX_INDEX_CACHE_LIFETIME + config-default@: system.flex.cache.index.lifetime + validate: + type: int + + object.cache.object.enabled: + type: toggle + toggleable: true + label: PLUGIN_ADMIN.FLEX_OBJECT_CACHE_ENABLED + highlight: 1 + config-default@: system.flex.cache.object.enabled + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + object.cache.object.lifetime: + type: text + toggleable: true + label: PLUGIN_ADMIN.FLEX_OBJECT_CACHE_LIFETIME + config-default@: system.flex.cache.object.lifetime + validate: + type: int + + object.cache.render.enabled: + type: toggle + toggleable: true + label: PLUGIN_ADMIN.FLEX_RENDER_CACHE_ENABLED + highlight: 1 + config-default@: system.flex.cache.render.enabled + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + object.cache.render.lifetime: + type: text + toggleable: true + label: PLUGIN_ADMIN.FLEX_RENDER_CACHE_LIFETIME + config-default@: system.flex.cache.render.lifetime + validate: + type: int diff --git a/system/blueprints/flex/user-accounts.yaml b/system/blueprints/flex/user-accounts.yaml new file mode 100644 index 0000000..74baeb6 --- /dev/null +++ b/system/blueprints/flex/user-accounts.yaml @@ -0,0 +1,155 @@ +title: User Accounts +description: Manage your User Accounts in Flex. +type: flex-objects + +# Extends user account +extends@: + type: account + context: blueprints://user + +# +# HIGHLY SPECIALIZED FLEX TYPE, AVOID USING USER ACCOUNTS AS BASE FOR YOUR OWN TYPE. +# + +# Flex configuration +config: + # Administration Configuration (needs Flex Objects plugin) + admin: + # Admin router + router: + path: '/accounts/users' + actions: + configure: + path: '/accounts/configure' + redirects: + '/user': '/accounts/users' + '/accounts': '/accounts/users' + + # Permissions + permissions: + # Primary permissions + admin.users: + type: crudl + label: User Accounts + admin.configuration.users: + type: default + label: Accounts Configuration + + # Admin menu + menu: + base: + location: '/accounts' + route: '/accounts/users' + index: 0 + title: PLUGIN_ADMIN.ACCOUNTS + icon: fa-users + authorize: ['admin.users.list', 'admin.super'] + priority: 6 + + # Admin template type (folder) + template: user-accounts + + # List view + list: + # Fields shown in the list view + fields: + username: + link: edit + search: true + field: + label: PLUGIN_ADMIN.USERNAME + email: + search: true + fullname: + search: true + # Extra options + options: + per_page: 20 + order: + by: username + dir: asc + + # Edit view + edit: + title: + template: "{{ form.value('fullname') ?? form.value('username') }} <{{ form.value('email') }}>" + + # Configure view + configure: + hidden: true + authorize: 'admin.configuration.users' + form: 'accounts' + title: + template: "{{ 'PLUGIN_ADMIN.ACCOUNTS'|tu }} {{ 'PLUGIN_ADMIN.CONFIGURATION'|tu }}" + + # Site Configuration + site: + # Hide from flex types + hidden: true + templates: + collection: + # Lookup for the template layout files for collections of objects + paths: + - 'flex/{TYPE}/collection/{LAYOUT}{EXT}' + object: + # Lookup for the template layout files for objects + paths: + - 'flex/{TYPE}/object/{LAYOUT}{EXT}' + defaults: + # Default template {TYPE}; overridden by filename of this blueprint if template folder exists + type: user-accounts + # Default template {LAYOUT}; can be overridden in render calls (usually Twig in templates) + layout: default + + # Data Configuration + data: + object: 'Grav\Common\Flex\Types\Users\UserObject' + collection: 'Grav\Common\Flex\Types\Users\UserCollection' + index: 'Grav\Common\Flex\Types\Users\UserIndex' + storage: + class: 'Grav\Common\Flex\Types\Users\Storage\UserFileStorage' + options: + formatter: + class: 'Grav\Framework\File\Formatter\YamlFormatter' + folder: 'account://' + pattern: '{FOLDER}/{KEY}{EXT}' + indexed: true + key: username + case_sensitive: false + search: + options: + contains: 1 + fields: + - key + - email + - username + - fullname + + relationships: + media: + type: media + cardinality: to-many + avatar: + type: media + cardinality: to-one +# roles: +# type: user-groups +# cardinality: to-many + +blueprints: + configure: + fields: + import@: + type: configure/compat + context: blueprints://flex + +# Regular form definition +form: + fields: + username: + flex-disabled@: exists + disabled: false + flex-readonly@: exists + readonly: false + validate: + required: true diff --git a/system/blueprints/flex/user-groups.yaml b/system/blueprints/flex/user-groups.yaml new file mode 100644 index 0000000..a5d348b --- /dev/null +++ b/system/blueprints/flex/user-groups.yaml @@ -0,0 +1,124 @@ +title: User Groups +description: Manage your User Groups in Flex. +type: flex-objects + +# Extends user group +extends@: + type: group + context: blueprints://user + +# Flex configuration +config: + # Administration Configuration (needs Flex Objects plugin) + admin: + # Admin router + router: + path: '/accounts/groups' + actions: + configure: + path: '/accounts/configure' + redirects: + '/groups': '/accounts/groups' + '/accounts': '/accounts/groups' + + # Permissions + permissions: + # Primary permissions + admin.users: + type: crudl + label: User Accounts + admin.configuration.users: + type: default + label: Accounts Configuration + + # Admin menu + menu: + base: + location: '/accounts' + route: '/accounts/groups' + index: 1 + title: PLUGIN_ADMIN.ACCOUNTS + icon: fa-users + authorize: ['admin.users.list', 'admin.super'] + priority: 6 + + # Admin template type (folder) + template: user-groups + + # List view + list: + # Fields shown in the list view + fields: + groupname: + link: edit + search: true + readableName: + search: true + description: + search: true + # Extra options + options: + per_page: 20 + order: + by: groupname + dir: asc + + # Edit view + edit: + title: + template: "{{ form.value('readableName') ?? form.value('groupname') }}" + + # Configure view + configure: + hidden: true + authorize: 'admin.configuration.users' + form: 'accounts' + title: + template: "{{ 'PLUGIN_ADMIN.ACCOUNTS'|tu }} {{ 'PLUGIN_ADMIN.CONFIGURATION'|tu }}" + + # Site Configuration + site: + # Hide from flex types + hidden: true + templates: + collection: + # Lookup for the template layout files for collections of objects + paths: + - 'flex/{TYPE}/collection/{LAYOUT}{EXT}' + object: + # Lookup for the template layout files for objects + paths: + - 'flex/{TYPE}/object/{LAYOUT}{EXT}' + defaults: + # Default template {TYPE}; overridden by filename of this blueprint if template folder exists + type: user-groups + # Default template {LAYOUT}; can be overridden in render calls (usually Twig in templates) + layout: default + + # Data Configuration + data: + object: 'Grav\Common\Flex\Types\UserGroups\UserGroupObject' + collection: 'Grav\Common\Flex\Types\UserGroups\UserGroupCollection' + index: 'Grav\Common\Flex\Types\UserGroups\UserGroupIndex' + storage: + class: 'Grav\Framework\Flex\Storage\SimpleStorage' + options: + formatter: + class: 'Grav\Framework\File\Formatter\YamlFormatter' + folder: 'user://config/groups.yaml' + key: groupname + search: + options: + contains: 1 + fields: + - key + - groupname + - readableName + - description + +blueprints: + configure: + fields: + import@: + type: configure/compat + context: blueprints://flex diff --git a/system/blueprints/pages/default.yaml b/system/blueprints/pages/default.yaml new file mode 100644 index 0000000..a573a83 --- /dev/null +++ b/system/blueprints/pages/default.yaml @@ -0,0 +1,381 @@ +title: PLUGIN_ADMIN.DEFAULT + +rules: + slug: + pattern: '[a-zA-Zа-яA-Я0-9_\-]+' + min: 1 + max: 200 + +form: + validation: loose + + fields: + + tabs: + type: tabs + active: 1 + + fields: + content: + type: tab + title: PLUGIN_ADMIN.CONTENT + + fields: + xss_check: + type: xss + + header.title: + type: text + autofocus: true + style: vertical + label: PLUGIN_ADMIN.TITLE + + content: + type: markdown + validate: + type: textarea + + header.media_order: + type: pagemedia + label: PLUGIN_ADMIN.PAGE_MEDIA + + options: + type: tab + title: PLUGIN_ADMIN.OPTIONS + + fields: + + publishing: + type: section + title: PLUGIN_ADMIN.PUBLISHING + underline: true + + fields: + header.published: + type: toggle + toggleable: true + label: PLUGIN_ADMIN.PUBLISHED + help: PLUGIN_ADMIN.PUBLISHED_HELP + highlight: 1 + size: medium + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + header.date: + type: datetime + label: PLUGIN_ADMIN.DATE + toggleable: true + help: PLUGIN_ADMIN.DATE_HELP + + header.publish_date: + type: datetime + label: PLUGIN_ADMIN.PUBLISHED_DATE + toggleable: true + help: PLUGIN_ADMIN.PUBLISHED_DATE_HELP + + header.unpublish_date: + type: datetime + label: PLUGIN_ADMIN.UNPUBLISHED_DATE + toggleable: true + help: PLUGIN_ADMIN.UNPUBLISHED_DATE_HELP + + header.metadata: + toggleable: true + type: array + label: PLUGIN_ADMIN.METADATA + help: PLUGIN_ADMIN.METADATA_HELP + placeholder_key: PLUGIN_ADMIN.METADATA_KEY + placeholder_value: PLUGIN_ADMIN.METADATA_VALUE + + taxonomies: + type: section + title: PLUGIN_ADMIN.TAXONOMIES + underline: true + + fields: + header.taxonomy: + type: taxonomy + label: PLUGIN_ADMIN.TAXONOMY + multiple: true + validate: + type: array + + advanced: + type: tab + title: PLUGIN_ADMIN.ADVANCED + + fields: + columns: + type: columns + fields: + column1: + type: column + fields: + + settings: + type: section + title: PLUGIN_ADMIN.SETTINGS + underline: true + + folder: + type: folder-slug + label: PLUGIN_ADMIN.FOLDER_NAME + validate: + rule: slug + + route: + type: parents + label: PLUGIN_ADMIN.PARENT + classes: fancy + + name: + type: select + classes: fancy + label: PLUGIN_ADMIN.PAGE_FILE + help: PLUGIN_ADMIN.PAGE_FILE_HELP + default: default + data-options@: '\Grav\Common\Page\Pages::pageTypes' + + header.body_classes: + type: text + label: PLUGIN_ADMIN.BODY_CLASSES + + + column2: + type: column + + fields: + order_title: + type: section + title: PLUGIN_ADMIN.ORDERING + underline: true + + ordering: + type: toggle + label: PLUGIN_ADMIN.FOLDER_NUMERIC_PREFIX + help: PLUGIN_ADMIN.FOLDER_NUMERIC_PREFIX_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + order: + type: order + label: PLUGIN_ADMIN.SORTABLE_PAGES + sitemap: + + overrides: + type: section + title: PLUGIN_ADMIN.OVERRIDES + underline: true + + fields: + + header.dateformat: + toggleable: true + type: select + size: medium + selectize: + create: true + label: PLUGIN_ADMIN.DEFAULT_DATE_FORMAT + help: PLUGIN_ADMIN.DEFAULT_DATE_FORMAT_HELP + placeholder: PLUGIN_ADMIN.DEFAULT_DATE_FORMAT_PLACEHOLDER + data-options@: '\Grav\Common\Utils::dateFormats' + validate: + type: string + + header.menu: + type: text + label: PLUGIN_ADMIN.MENU + toggleable: true + help: PLUGIN_ADMIN.MENU_HELP + + header.slug: + type: text + label: PLUGIN_ADMIN.SLUG + toggleable: true + help: PLUGIN_ADMIN.SLUG_HELP + validate: + message: PLUGIN_ADMIN.SLUG_VALIDATE_MESSAGE + rule: slug + + header.redirect: + type: text + label: PLUGIN_ADMIN.REDIRECT + toggleable: true + help: PLUGIN_ADMIN.REDIRECT_HELP + + header.process: + type: checkboxes + label: PLUGIN_ADMIN.PROCESS + toggleable: true + config-default@: system.pages.process + default: + markdown: true + twig: false + options: + markdown: Markdown + twig: Twig + use: keys + + header.twig_first: + type: toggle + toggleable: true + label: PLUGIN_ADMIN.TWIG_FIRST + help: PLUGIN_ADMIN.TWIG_FIRST_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + header.never_cache_twig: + type: toggle + toggleable: true + label: PLUGIN_ADMIN.NEVER_CACHE_TWIG + help: PLUGIN_ADMIN.NEVER_CACHE_TWIG_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + header.child_type: + type: select + toggleable: true + label: PLUGIN_ADMIN.DEFAULT_CHILD_TYPE + default: default + placeholder: PLUGIN_ADMIN.USE_GLOBAL + data-options@: '\Grav\Common\Page\Pages::types' + + header.routable: + type: toggle + toggleable: true + label: PLUGIN_ADMIN.ROUTABLE + help: PLUGIN_ADMIN.ROUTABLE_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + header.cache_enable: + type: toggle + toggleable: true + label: PLUGIN_ADMIN.CACHING + highlight: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + header.visible: + type: toggle + toggleable: true + label: PLUGIN_ADMIN.VISIBLE + help: PLUGIN_ADMIN.VISIBLE_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + header.debugger: + type: toggle + toggleable: true + label: PLUGIN_ADMIN.DEBUGGER + help: PLUGIN_ADMIN.DEBUGGER_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + header.template: + type: text + toggleable: true + label: PLUGIN_ADMIN.DISPLAY_TEMPLATE + + header.append_url_extension: + type: text + label: PLUGIN_ADMIN.APPEND_URL_EXT + toggleable: true + help: PLUGIN_ADMIN.APPEND_URL_EXT_HELP + + routes_only: + type: section + title: PLUGIN_ADMIN.ROUTE_OVERRIDES + underline: true + + fields: + + header.redirect_default_route: + type: toggle + toggleable: true + label: PLUGIN_ADMIN.REDIRECT_DEFAULT_ROUTE + help: PLUGIN_ADMIN.REDIRECT_DEFAULT_ROUTE_HELP + config-highlight@: system.pages.redirect_default_route + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + header.routes.default: + type: text + toggleable: true + label: PLUGIN_ADMIN.ROUTE_DEFAULT + + header.routes.canonical: + type: text + toggleable: true + label: PLUGIN_ADMIN.ROUTE_CANONICAL + + header.routes.aliases: + type: array + toggleable: true + value_only: true + size: large + label: PLUGIN_ADMIN.ROUTE_ALIASES + + + admin_only: + type: section + title: PLUGIN_ADMIN.ADMIN_SPECIFIC_OVERRIDES + underline: true + + fields: + + header.admin.children_display_order: + type: select + label: PLUGIN_ADMIN.ADMIN_CHILDREN_DISPLAY_ORDER + help: PLUGIN_ADMIN.ADMIN_CHILDREN_DISPLAY_ORDER_HELP + toggleable: true + classes: fancy + default: 'collection' + options: + 'default': 'Ordered by Folder name (default)' + 'collection': 'Ordered by Collection definition' + + + header.order_by: + type: hidden + + header.order_manual: + type: hidden + validate: + type: commalist + + blueprint: + type: blueprint diff --git a/system/blueprints/pages/external.yaml b/system/blueprints/pages/external.yaml new file mode 100644 index 0000000..d3bb57a --- /dev/null +++ b/system/blueprints/pages/external.yaml @@ -0,0 +1,52 @@ +title: PLUGIN_ADMIN.EXTERNAL +extends@: + type: default + context: blueprints://pages + +form: + validation: loose + fields: + + tabs: + type: tabs + active: 1 + + fields: + + content: + fields: + + header.title: + type: text + autofocus: true + style: horizontal + label: PLUGIN_ADMIN.TITLE + + content: + unset@: true + + header.media_order: + unset@: true + + header.external_url: + type: text + label: PLUGIN_ADMIN.EXTERNAL_URL + placeholder: https://getgrav.org + validate: + required: true + + options: + fields: + + publishing: + fields: + + header.date: + unset@: true + + header.metadata: + unset@: true + + taxonomies: + unset@: true + diff --git a/system/blueprints/pages/modular.yaml b/system/blueprints/pages/modular.yaml new file mode 100644 index 0000000..5461b23 --- /dev/null +++ b/system/blueprints/pages/modular.yaml @@ -0,0 +1,36 @@ +title: PLUGIN_ADMIN.MODULE +extends@: default + +form: + fields: + tabs: + type: tabs + active: 1 + + fields: + content: + fields: + + modular_title: + type: spacer + title: PLUGIN_ADMIN.MODULE_SETUP + + header.content.items: + type: text + label: PLUGIN_ADMIN.ITEMS + default: '@self.modular' + size: medium + + header.content.order.by: + type: text + label: PLUGIN_ADMIN.ORDER_BY + placeholder: date + help: + size: small + + header.content.order.dir: + type: text + label: PLUGIN_ADMIN.ORDER + help: '"desc" or "asc" are valid values' + placeholder: desc + size: small diff --git a/system/blueprints/pages/partials/security.yaml b/system/blueprints/pages/partials/security.yaml new file mode 100644 index 0000000..26d8c7d --- /dev/null +++ b/system/blueprints/pages/partials/security.yaml @@ -0,0 +1,67 @@ +form: + fields: + _site: + type: section + title: PLUGIN_ADMIN.PAGE_ACCESS + underline: true + + fields: + + header.login.visibility_requires_access: + type: toggle + toggleable: true + label: PLUGIN_ADMIN.PAGE_VISIBILITY_REQUIRES_ACCESS + help: PLUGIN_ADMIN.PAGE_VISIBILITY_REQUIRES_ACCESS_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + + header.access: + type: acl_picker + label: PLUGIN_ADMIN.PAGE_ACCESS + help: PLUGIN_ADMIN.PAGE_ACCESS_HELP + ignore_empty: true + data_type: access + validate: + type: array + value_type: bool + + _admin: + security@: {or: [admin.super, admin.configuration.pages]} + type: section + title: PLUGIN_ADMIN.PAGE PERMISSIONS + underline: true + + fields: + + header.permissions.inherit: + type: toggle + toggleable: true + label: PLUGIN_ADMIN.PAGE_INHERIT_PERMISSIONS + help: PLUGIN_ADMIN.PAGE_INHERIT_PERMISSIONS_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + header.permissions.authors: + type: array + toggleable: true + value_only: true + placeholder_value: PLUGIN_ADMIN.USERNAME + label: PLUGIN_ADMIN.PAGE_AUTHORS + help: PLUGIN_ADMIN.PAGE_AUTHORS_HELP + + header.permissions.groups: + ignore@: true + type: acl_picker + label: PLUGIN_ADMIN.PAGE_GROUPS + help: PLUGIN_ADMIN.PAGE_GROUPS_HELP + ignore_empty: true + data_type: permissions diff --git a/system/blueprints/pages/root.yaml b/system/blueprints/pages/root.yaml new file mode 100644 index 0000000..5abc516 --- /dev/null +++ b/system/blueprints/pages/root.yaml @@ -0,0 +1,16 @@ +title: PLUGIN_ADMIN.ROOT + +rules: + slug: + pattern: '[a-zA-Zа-яA-Я0-9_\-]+' + min: 1 + max: 200 + +form: + validation: loose + + fields: + + tabs: + type: tabs + active: 1 diff --git a/system/blueprints/user/account.yaml b/system/blueprints/user/account.yaml new file mode 100644 index 0000000..ef5f25b --- /dev/null +++ b/system/blueprints/user/account.yaml @@ -0,0 +1,157 @@ +title: Account +form: + validation: loose + + fields: + + info: + type: userinfo + size: large + + avatar: + type: file + size: large + destination: 'account://avatars' + multiple: false + random_name: true + + multiavatar_only: + type: conditional + condition: config.system.accounts.avatar == 'multiavatar' + fields: + avatar_hash: + type: text + label: '' + placeholder: 'e.g. dceaadcfda491f4e45' + description: PLUGIN_ADMIN.AVATAR_HASH + size: large + + content: + type: section + title: PLUGIN_ADMIN.ACCOUNT + underline: true + + username: + type: text + size: large + label: PLUGIN_ADMIN.USERNAME + disabled: true + readonly: true + + email: + type: email + size: large + label: PLUGIN_ADMIN.EMAIL + validate: + type: email + message: PLUGIN_ADMIN.EMAIL_VALIDATION_MESSAGE + required: true + + password: + type: password + size: large + label: PLUGIN_ADMIN.PASSWORD + autocomplete: new-password + validate: + required: false + message: PLUGIN_ADMIN.PASSWORD_VALIDATION_MESSAGE + config-pattern@: system.pwd_regex + + fullname: + type: text + size: large + label: PLUGIN_ADMIN.FULL_NAME + validate: + required: true + + title: + type: text + size: large + label: PLUGIN_ADMIN.TITLE + + language: + type: select + label: PLUGIN_ADMIN.LANGUAGE + size: medium + classes: fancy + data-options@: '\Grav\Plugin\Admin\Admin::adminLanguages' + default: 'en' + help: PLUGIN_ADMIN.LANGUAGE_HELP + + content_editor: + type: select + label: PLUGIN_ADMIN.CONTENT_EDITOR + size: medium + classes: fancy + data-options@: 'Grav\Plugin\Admin\Admin::contentEditor' + default: 'default' + help: PLUGIN_ADMIN.CONTENT_EDITOR_HELP + + twofa_check: + type: conditional + condition: config.plugins.admin.twofa_enabled + + fields: + + twofa: + title: PLUGIN_ADMIN.2FA_TITLE + type: section + underline: true + + twofa_enabled: + type: toggle + label: PLUGIN_ADMIN.2FA_ENABLED + classes: twofa-toggle + highlight: 1 + default: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + + twofa_secret: + type: 2fa_secret + outerclasses: 'twofa-secret' + markdown: true + label: PLUGIN_ADMIN.2FA_SECRET + sublabel: PLUGIN_ADMIN.2FA_SECRET_HELP + + yubikey_id: + type: text + label: PLUGIN_ADMIN.YUBIKEY_ID + description: PLUGIN_ADMIN.YUBIKEY_HELP + size: small + maxlength: 12 + + + + security: + security@: admin.super + title: PLUGIN_ADMIN.ACCESS_LEVELS + type: section + underline: true + + fields: + groups: + security@: admin.super + type: select + multiple: true + size: large + label: PLUGIN_ADMIN.GROUPS + data-options@: '\Grav\Common\User\Group::groupNames' + classes: fancy + help: PLUGIN_ADMIN.GROUPS_HELP + validate: + type: commalist + + access: + security@: admin.super + type: permissions + check_authorize: true + label: PLUGIN_ADMIN.PERMISSIONS + ignore_empty: true + validate: + type: array + value_type: bool diff --git a/system/blueprints/user/account_new.yaml b/system/blueprints/user/account_new.yaml new file mode 100644 index 0000000..1b22f93 --- /dev/null +++ b/system/blueprints/user/account_new.yaml @@ -0,0 +1,18 @@ +title: PLUGIN_ADMIN.ADD_ACCOUNT + +form: + validation: loose + fields: + + content: + type: section + title: PLUGIN_ADMIN.ADD_ACCOUNT + + username: + type: text + label: PLUGIN_ADMIN.USERNAME + help: PLUGIN_ADMIN.USERNAME_HELP + unset-disabled@: true + unset-readonly@: true + validate: + required: true diff --git a/system/blueprints/user/group.yaml b/system/blueprints/user/group.yaml new file mode 100644 index 0000000..61f0227 --- /dev/null +++ b/system/blueprints/user/group.yaml @@ -0,0 +1,55 @@ +title: Group +rules: + slug: + pattern: '[a-zA-Zа-яA-Я0-9_\-]+' + min: 1 + max: 200 + +form: + validation: loose + + fields: + groupname: + type: text + size: large + label: PLUGIN_ADMIN.GROUP_NAME + flex-disabled@: exists + flex-readonly@: exists + validate: + required: true + rule: slug + + readableName: + type: text + size: large + label: PLUGIN_ADMIN.DISPLAY_NAME + + description: + type: text + size: large + label: PLUGIN_ADMIN.DESCRIPTION + + icon: + type: text + size: small + label: PLUGIN_ADMIN.ICON + + enabled: + type: toggle + label: PLUGIN_ADMIN.ENABLED + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + access: + type: permissions + check_authorize: false + label: PLUGIN_ADMIN.PERMISSIONS + ignore_empty: true + validate: + type: array + value_type: bool diff --git a/system/blueprints/user/group_new.yaml b/system/blueprints/user/group_new.yaml new file mode 100644 index 0000000..baa37c3 --- /dev/null +++ b/system/blueprints/user/group_new.yaml @@ -0,0 +1,23 @@ +title: PLUGIN_ADMIN_PRO.ADD_GROUP + +rules: + slug: + pattern: '[a-zA-Zа-яA-Я0-9_\-]+' + min: 1 + max: 200 + +form: + validation: loose + fields: + + content: + type: section + title: PLUGIN_ADMIN_PRO.ADD_GROUP + + groupname: + type: text + label: PLUGIN_ADMIN_PRO.GROUP_NAME + help: PLUGIN_ADMIN_PRO.GROUP_NAME_HELP + validate: + required: true + rule: slug diff --git a/system/config/backups.yaml b/system/config/backups.yaml new file mode 100644 index 0000000..0d3c41e --- /dev/null +++ b/system/config/backups.yaml @@ -0,0 +1,15 @@ +purge: + trigger: space + max_backups_count: 25 + max_backups_space: 5 + max_backups_time: 365 + +profiles: + - + name: 'Default Site Backup' + root: '/' + schedule: false + schedule_at: '0 3 * * *' + exclude_paths: "/backup\r\n/cache\r\n/images\r\n/logs\r\n/tmp" + exclude_files: ".DS_Store\r\n.git\r\n.svn\r\n.hg\r\n.idea\r\n.vscode\r\nnode_modules" + diff --git a/system/config/media.yaml b/system/config/media.yaml new file mode 100644 index 0000000..e231b33 --- /dev/null +++ b/system/config/media.yaml @@ -0,0 +1,223 @@ +types: + defaults: + type: file + thumb: media/thumb.png + mime: application/octet-stream + image: + filters: + default: + - enableProgressive + + jpg: + type: image + thumb: media/thumb-jpg.png + mime: image/jpeg + jpe: + type: image + thumb: media/thumb-jpg.png + mime: image/jpeg + jpeg: + type: image + thumb: media/thumb-jpg.png + mime: image/jpeg + png: + type: image + thumb: media/thumb-png.png + mime: image/png + webp: + type: image + thumb: media/thumb-webp.png + mime: image/webp + avif: + type: image + thumb: media/thumb.png + mime: image/avif + gif: + type: animated + thumb: media/thumb-gif.png + mime: image/gif + svg: + type: vector + thumb: media/thumb-svg.png + mime: image/svg+xml + mp4: + type: video + thumb: media/thumb-mp4.png + mime: video/mp4 + mov: + type: video + thumb: media/thumb-mov.png + mime: video/quicktime + m4v: + type: video + thumb: media/thumb-m4v.png + mime: video/x-m4v + swf: + type: video + thumb: media/thumb-swf.png + mime: video/x-flv + flv: + type: video + thumb: media/thumb-flv.png + mime: video/x-flv + webm: + type: video + thumb: media/thumb-webm.png + mime: video/webm + ogv: + type: video + thumb: media/thumb-ogg.png + mime: video/ogg + mp3: + type: audio + thumb: media/thumb-mp3.png + mime: audio/mp3 + ogg: + type: audio + thumb: media/thumb-ogg.png + mime: audio/ogg + wma: + type: audio + thumb: media/thumb-wma.png + mime: audio/wma + m4a: + type: audio + thumb: media/thumb-m4a.png + mime: audio/m4a + wav: + type: audio + thumb: media/thumb-wav.png + mime: audio/wav + aiff: + type: audio + thumb: media/thumb-aif.png + mime: audio/aiff + aif: + type: audio + thumb: media/thumb-aif.png + mime: audio/aiff + txt: + type: file + thumb: media/thumb-txt.png + mime: text/plain + xml: + type: file + thumb: media/thumb-xml.png + mime: application/xml + doc: + type: file + thumb: media/thumb-doc.png + mime: application/msword + docx: + type: file + thumb: media/thumb-docx.png + mime: application/vnd.openxmlformats-officedocument.wordprocessingml.document + xls: + type: file + thumb: media/thumb-xls.png + mime: application/vnd.ms-excel + xlsx: + type: file + thumb: media/thumb-xlsx.png + mime: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet + ppt: + type: file + thumb: media/thumb-ppt.png + mime: application/vnd.ms-powerpoint + pptx: + type: file + thumb: media/thumb-pptx.png + mime: application/vnd.openxmlformats-officedocument.presentationml.presentation + pps: + type: file + thumb: media/thumb-pps.png + mime: application/vnd.ms-powerpoint + rtf: + type: file + thumb: media/thumb-rtf.png + mime: application/rtf + bmp: + type: file + thumb: media/thumb-bmp.png + mime: image/bmp + tiff: + type: file + thumb: media/thumb-tiff.png + mime: image/tiff + mpeg: + type: file + thumb: media/thumb-mpg.png + mime: video/mpeg + mpg: + type: file + thumb: media/thumb-mpg.png + mime: video/mpeg + mpe: + type: file + thumb: media/thumb-mpe.png + mime: video/mpeg + avi: + type: file + thumb: media/thumb-avi.png + mime: video/msvideo + wmv: + type: file + thumb: media/thumb-wmv.png + mime: video/x-ms-wmv + html: + type: file + thumb: media/thumb-html.png + mime: text/html + htm: + type: file + thumb: media/thumb-html.png + mime: text/html + ics: + type: iCal + thumb: media/thumb-ics.png + mime: text/calendar + pdf: + type: file + thumb: media/thumb-pdf.png + mime: application/pdf + ai: + type: file + thumb: media/thumb-ai.png + mime: image/ai + psd: + type: file + thumb: media/thumb-psd.png + mime: image/psd + zip: + type: file + thumb: media/thumb-zip.png + mime: application/zip + 7z: + type: file + thumb: media/thumb-7z.png + mime: application/x-7z-compressed + gz: + type: file + thumb: media/thumb-gz.png + mime: application/x-gzip + tar: + type: file + thumb: media/thumb-tar.png + mime: application/x-tar + css: + type: file + thumb: media/thumb-css.png + mime: text/css + js: + type: file + thumb: media/thumb-js.png + mime: text/javascript + json: + type: file + thumb: media/thumb-json.png + mime: application/json + vcf: + type: file + thumb: media/thumb-vcf.png + mime: text/x-vcard + diff --git a/system/config/mime.yaml b/system/config/mime.yaml new file mode 100644 index 0000000..3143c67 --- /dev/null +++ b/system/config/mime.yaml @@ -0,0 +1,1986 @@ +types: + '123': + - application/vnd.lotus-1-2-3 + wof: + - application/font-woff + php: + - application/php + - application/x-httpd-php + - application/x-httpd-php-source + - application/x-php + - text/php + - text/x-php + otf: + - application/x-font-otf + - font/otf + ttf: + - application/x-font-ttf + - font/ttf + ttc: + - application/x-font-ttf + - font/collection + zip: + - application/x-gzip + - application/zip + - application/x-zip-compressed + amr: + - audio/amr + mp3: + - audio/mpeg + mpga: + - audio/mpeg + mp2: + - audio/mpeg + mp2a: + - audio/mpeg + m2a: + - audio/mpeg + m3a: + - audio/mpeg + jpg: + - image/jpeg + jpeg: + - image/jpeg + jpe: + - image/jpeg + bmp: + - image/x-ms-bmp + - image/bmp + ez: + - application/andrew-inset + aw: + - application/applixware + atom: + - application/atom+xml + atomcat: + - application/atomcat+xml + atomsvc: + - application/atomsvc+xml + ccxml: + - application/ccxml+xml + cdmia: + - application/cdmi-capability + cdmic: + - application/cdmi-container + cdmid: + - application/cdmi-domain + cdmio: + - application/cdmi-object + cdmiq: + - application/cdmi-queue + cu: + - application/cu-seeme + davmount: + - application/davmount+xml + dbk: + - application/docbook+xml + dssc: + - application/dssc+der + xdssc: + - application/dssc+xml + ecma: + - application/ecmascript + emma: + - application/emma+xml + epub: + - application/epub+zip + exi: + - application/exi + pfr: + - application/font-tdpfr + gml: + - application/gml+xml + gpx: + - application/gpx+xml + gxf: + - application/gxf + stk: + - application/hyperstudio + ink: + - application/inkml+xml + inkml: + - application/inkml+xml + ipfix: + - application/ipfix + jar: + - application/java-archive + ser: + - application/java-serialized-object + class: + - application/java-vm + js: + - application/javascript + json: + - application/json + jsonml: + - application/jsonml+json + lostxml: + - application/lost+xml + hqx: + - application/mac-binhex40 + cpt: + - application/mac-compactpro + mads: + - application/mads+xml + mrc: + - application/marc + mrcx: + - application/marcxml+xml + ma: + - application/mathematica + nb: + - application/mathematica + mb: + - application/mathematica + mathml: + - application/mathml+xml + mbox: + - application/mbox + mscml: + - application/mediaservercontrol+xml + metalink: + - application/metalink+xml + meta4: + - application/metalink4+xml + mets: + - application/mets+xml + mods: + - application/mods+xml + m21: + - application/mp21 + mp21: + - application/mp21 + mp4s: + - application/mp4 + doc: + - application/msword + dot: + - application/msword + mxf: + - application/mxf + bin: + - application/octet-stream + dms: + - application/octet-stream + lrf: + - application/octet-stream + mar: + - application/octet-stream + so: + - application/octet-stream + dist: + - application/octet-stream + distz: + - application/octet-stream + pkg: + - application/octet-stream + bpk: + - application/octet-stream + dump: + - application/octet-stream + elc: + - application/octet-stream + deploy: + - application/octet-stream + oda: + - application/oda + opf: + - application/oebps-package+xml + ogx: + - application/ogg + omdoc: + - application/omdoc+xml + onetoc: + - application/onenote + onetoc2: + - application/onenote + onetmp: + - application/onenote + onepkg: + - application/onenote + oxps: + - application/oxps + xer: + - application/patch-ops-error+xml + pdf: + - application/pdf + pgp: + - application/pgp-encrypted + asc: + - application/pgp-signature + sig: + - application/pgp-signature + prf: + - application/pics-rules + p10: + - application/pkcs10 + p7m: + - application/pkcs7-mime + p7c: + - application/pkcs7-mime + p7s: + - application/pkcs7-signature + p8: + - application/pkcs8 + ac: + - application/pkix-attr-cert + cer: + - application/pkix-cert + crl: + - application/pkix-crl + pkipath: + - application/pkix-pkipath + pki: + - application/pkixcmp + pls: + - application/pls+xml + ai: + - application/postscript + eps: + - application/postscript + ps: + - application/postscript + cww: + - application/prs.cww + pskcxml: + - application/pskc+xml + rdf: + - application/rdf+xml + rif: + - application/reginfo+xml + rnc: + - application/relax-ng-compact-syntax + rl: + - application/resource-lists+xml + rld: + - application/resource-lists-diff+xml + rs: + - application/rls-services+xml + gbr: + - application/rpki-ghostbusters + mft: + - application/rpki-manifest + roa: + - application/rpki-roa + rsd: + - application/rsd+xml + rss: + - application/rss+xml + rtf: + - application/rtf + sbml: + - application/sbml+xml + scq: + - application/scvp-cv-request + scs: + - application/scvp-cv-response + spq: + - application/scvp-vp-request + spp: + - application/scvp-vp-response + sdp: + - application/sdp + setpay: + - application/set-payment-initiation + setreg: + - application/set-registration-initiation + shf: + - application/shf+xml + smi: + - application/smil+xml + smil: + - application/smil+xml + rq: + - application/sparql-query + srx: + - application/sparql-results+xml + gram: + - application/srgs + grxml: + - application/srgs+xml + sru: + - application/sru+xml + ssdl: + - application/ssdl+xml + ssml: + - application/ssml+xml + tei: + - application/tei+xml + teicorpus: + - application/tei+xml + tfi: + - application/thraud+xml + tsd: + - application/timestamped-data + plb: + - application/vnd.3gpp.pic-bw-large + psb: + - application/vnd.3gpp.pic-bw-small + pvb: + - application/vnd.3gpp.pic-bw-var + tcap: + - application/vnd.3gpp2.tcap + pwn: + - application/vnd.3m.post-it-notes + aso: + - application/vnd.accpac.simply.aso + imp: + - application/vnd.accpac.simply.imp + acu: + - application/vnd.acucobol + atc: + - application/vnd.acucorp + acutc: + - application/vnd.acucorp + air: + - application/vnd.adobe.air-application-installer-package+zip + fcdt: + - application/vnd.adobe.formscentral.fcdt + fxp: + - application/vnd.adobe.fxp + fxpl: + - application/vnd.adobe.fxp + xdp: + - application/vnd.adobe.xdp+xml + xfdf: + - application/vnd.adobe.xfdf + ahead: + - application/vnd.ahead.space + azf: + - application/vnd.airzip.filesecure.azf + azs: + - application/vnd.airzip.filesecure.azs + azw: + - application/vnd.amazon.ebook + acc: + - application/vnd.americandynamics.acc + ami: + - application/vnd.amiga.ami + apk: + - application/vnd.android.package-archive + cii: + - application/vnd.anser-web-certificate-issue-initiation + fti: + - application/vnd.anser-web-funds-transfer-initiation + atx: + - application/vnd.antix.game-component + mpkg: + - application/vnd.apple.installer+xml + m3u8: + - application/vnd.apple.mpegurl + swi: + - application/vnd.aristanetworks.swi + iota: + - application/vnd.astraea-software.iota + aep: + - application/vnd.audiograph + mpm: + - application/vnd.blueice.multipass + bmi: + - application/vnd.bmi + rep: + - application/vnd.businessobjects + cdxml: + - application/vnd.chemdraw+xml + mmd: + - application/vnd.chipnuts.karaoke-mmd + cdy: + - application/vnd.cinderella + cla: + - application/vnd.claymore + rp9: + - application/vnd.cloanto.rp9 + c4g: + - application/vnd.clonk.c4group + c4d: + - application/vnd.clonk.c4group + c4f: + - application/vnd.clonk.c4group + c4p: + - application/vnd.clonk.c4group + c4u: + - application/vnd.clonk.c4group + c11amc: + - application/vnd.cluetrust.cartomobile-config + c11amz: + - application/vnd.cluetrust.cartomobile-config-pkg + csp: + - application/vnd.commonspace + cdbcmsg: + - application/vnd.contact.cmsg + cmc: + - application/vnd.cosmocaller + clkx: + - application/vnd.crick.clicker + clkk: + - application/vnd.crick.clicker.keyboard + clkp: + - application/vnd.crick.clicker.palette + clkt: + - application/vnd.crick.clicker.template + clkw: + - application/vnd.crick.clicker.wordbank + wbs: + - application/vnd.criticaltools.wbs+xml + pml: + - application/vnd.ctc-posml + ppd: + - application/vnd.cups-ppd + car: + - application/vnd.curl.car + pcurl: + - application/vnd.curl.pcurl + dart: + - application/vnd.dart + rdz: + - application/vnd.data-vision.rdz + uvf: + - application/vnd.dece.data + uvvf: + - application/vnd.dece.data + uvd: + - application/vnd.dece.data + uvvd: + - application/vnd.dece.data + uvt: + - application/vnd.dece.ttml+xml + uvvt: + - application/vnd.dece.ttml+xml + uvx: + - application/vnd.dece.unspecified + uvvx: + - application/vnd.dece.unspecified + uvz: + - application/vnd.dece.zip + uvvz: + - application/vnd.dece.zip + fe_launch: + - application/vnd.denovo.fcselayout-link + dna: + - application/vnd.dna + mlp: + - application/vnd.dolby.mlp + dpg: + - application/vnd.dpgraph + dfac: + - application/vnd.dreamfactory + kpxx: + - application/vnd.ds-keypoint + ait: + - application/vnd.dvb.ait + svc: + - application/vnd.dvb.service + geo: + - application/vnd.dynageo + mag: + - application/vnd.ecowin.chart + nml: + - application/vnd.enliven + esf: + - application/vnd.epson.esf + msf: + - application/vnd.epson.msf + qam: + - application/vnd.epson.quickanime + slt: + - application/vnd.epson.salt + ssf: + - application/vnd.epson.ssf + es3: + - application/vnd.eszigno3+xml + et3: + - application/vnd.eszigno3+xml + ez2: + - application/vnd.ezpix-album + ez3: + - application/vnd.ezpix-package + fdf: + - application/vnd.fdf + mseed: + - application/vnd.fdsn.mseed + seed: + - application/vnd.fdsn.seed + dataless: + - application/vnd.fdsn.seed + gph: + - application/vnd.flographit + ftc: + - application/vnd.fluxtime.clip + fm: + - application/vnd.framemaker + frame: + - application/vnd.framemaker + maker: + - application/vnd.framemaker + book: + - application/vnd.framemaker + fnc: + - application/vnd.frogans.fnc + ltf: + - application/vnd.frogans.ltf + fsc: + - application/vnd.fsc.weblaunch + oas: + - application/vnd.fujitsu.oasys + oa2: + - application/vnd.fujitsu.oasys2 + oa3: + - application/vnd.fujitsu.oasys3 + fg5: + - application/vnd.fujitsu.oasysgp + bh2: + - application/vnd.fujitsu.oasysprs + ddd: + - application/vnd.fujixerox.ddd + xdw: + - application/vnd.fujixerox.docuworks + xbd: + - application/vnd.fujixerox.docuworks.binder + fzs: + - application/vnd.fuzzysheet + txd: + - application/vnd.genomatix.tuxedo + ggb: + - application/vnd.geogebra.file + ggt: + - application/vnd.geogebra.tool + gex: + - application/vnd.geometry-explorer + gre: + - application/vnd.geometry-explorer + gxt: + - application/vnd.geonext + g2w: + - application/vnd.geoplan + g3w: + - application/vnd.geospace + gmx: + - application/vnd.gmx + kml: + - application/vnd.google-earth.kml+xml + kmz: + - application/vnd.google-earth.kmz + gqf: + - application/vnd.grafeq + gqs: + - application/vnd.grafeq + gac: + - application/vnd.groove-account + ghf: + - application/vnd.groove-help + gim: + - application/vnd.groove-identity-message + grv: + - application/vnd.groove-injector + gtm: + - application/vnd.groove-tool-message + tpl: + - application/vnd.groove-tool-template + vcg: + - application/vnd.groove-vcard + hal: + - application/vnd.hal+xml + zmm: + - application/vnd.handheld-entertainment+xml + hbci: + - application/vnd.hbci + les: + - application/vnd.hhe.lesson-player + hpgl: + - application/vnd.hp-hpgl + hpid: + - application/vnd.hp-hpid + hps: + - application/vnd.hp-hps + jlt: + - application/vnd.hp-jlyt + pcl: + - application/vnd.hp-pcl + pclxl: + - application/vnd.hp-pclxl + sfd-hdstx: + - application/vnd.hydrostatix.sof-data + mpy: + - application/vnd.ibm.minipay + afp: + - application/vnd.ibm.modcap + listafp: + - application/vnd.ibm.modcap + list3820: + - application/vnd.ibm.modcap + irm: + - application/vnd.ibm.rights-management + sc: + - application/vnd.ibm.secure-container + icc: + - application/vnd.iccprofile + icm: + - application/vnd.iccprofile + igl: + - application/vnd.igloader + ivp: + - application/vnd.immervision-ivp + ivu: + - application/vnd.immervision-ivu + igm: + - application/vnd.insors.igm + xpw: + - application/vnd.intercon.formnet + xpx: + - application/vnd.intercon.formnet + i2g: + - application/vnd.intergeo + qbo: + - application/vnd.intu.qbo + qfx: + - application/vnd.intu.qfx + rcprofile: + - application/vnd.ipunplugged.rcprofile + irp: + - application/vnd.irepository.package+xml + xpr: + - application/vnd.is-xpr + fcs: + - application/vnd.isac.fcs + jam: + - application/vnd.jam + rms: + - application/vnd.jcp.javame.midlet-rms + jisp: + - application/vnd.jisp + joda: + - application/vnd.joost.joda-archive + ktz: + - application/vnd.kahootz + ktr: + - application/vnd.kahootz + karbon: + - application/vnd.kde.karbon + chrt: + - application/vnd.kde.kchart + kfo: + - application/vnd.kde.kformula + flw: + - application/vnd.kde.kivio + kon: + - application/vnd.kde.kontour + kpr: + - application/vnd.kde.kpresenter + kpt: + - application/vnd.kde.kpresenter + ksp: + - application/vnd.kde.kspread + kwd: + - application/vnd.kde.kword + kwt: + - application/vnd.kde.kword + htke: + - application/vnd.kenameaapp + kia: + - application/vnd.kidspiration + kne: + - application/vnd.kinar + knp: + - application/vnd.kinar + skp: + - application/vnd.koan + skd: + - application/vnd.koan + skt: + - application/vnd.koan + skm: + - application/vnd.koan + sse: + - application/vnd.kodak-descriptor + lasxml: + - application/vnd.las.las+xml + lbd: + - application/vnd.llamagraphics.life-balance.desktop + lbe: + - application/vnd.llamagraphics.life-balance.exchange+xml + apr: + - application/vnd.lotus-approach + pre: + - application/vnd.lotus-freelance + nsf: + - application/vnd.lotus-notes + org: + - application/vnd.lotus-organizer + scm: + - application/vnd.lotus-screencam + lwp: + - application/vnd.lotus-wordpro + portpkg: + - application/vnd.macports.portpkg + mcd: + - application/vnd.mcd + mc1: + - application/vnd.medcalcdata + cdkey: + - application/vnd.mediastation.cdkey + mwf: + - application/vnd.mfer + mfm: + - application/vnd.mfmp + flo: + - application/vnd.micrografx.flo + igx: + - application/vnd.micrografx.igx + mif: + - application/vnd.mif + daf: + - application/vnd.mobius.daf + dis: + - application/vnd.mobius.dis + mbk: + - application/vnd.mobius.mbk + mqy: + - application/vnd.mobius.mqy + msl: + - application/vnd.mobius.msl + plc: + - application/vnd.mobius.plc + txf: + - application/vnd.mobius.txf + mpn: + - application/vnd.mophun.application + mpc: + - application/vnd.mophun.certificate + xul: + - application/vnd.mozilla.xul+xml + cil: + - application/vnd.ms-artgalry + cab: + - application/vnd.ms-cab-compressed + xls: + - application/vnd.ms-excel + xlm: + - application/vnd.ms-excel + xla: + - application/vnd.ms-excel + xlc: + - application/vnd.ms-excel + xlt: + - application/vnd.ms-excel + xlw: + - application/vnd.ms-excel + xlam: + - application/vnd.ms-excel.addin.macroenabled.12 + xlsb: + - application/vnd.ms-excel.sheet.binary.macroenabled.12 + xlsm: + - application/vnd.ms-excel.sheet.macroenabled.12 + xltm: + - application/vnd.ms-excel.template.macroenabled.12 + eot: + - application/vnd.ms-fontobject + chm: + - application/vnd.ms-htmlhelp + ims: + - application/vnd.ms-ims + lrm: + - application/vnd.ms-lrm + thmx: + - application/vnd.ms-officetheme + cat: + - application/vnd.ms-pki.seccat + stl: + - application/vnd.ms-pki.stl + ppt: + - application/vnd.ms-powerpoint + pps: + - application/vnd.ms-powerpoint + pot: + - application/vnd.ms-powerpoint + ppam: + - application/vnd.ms-powerpoint.addin.macroenabled.12 + pptm: + - application/vnd.ms-powerpoint.presentation.macroenabled.12 + sldm: + - application/vnd.ms-powerpoint.slide.macroenabled.12 + ppsm: + - application/vnd.ms-powerpoint.slideshow.macroenabled.12 + potm: + - application/vnd.ms-powerpoint.template.macroenabled.12 + mpp: + - application/vnd.ms-project + mpt: + - application/vnd.ms-project + docm: + - application/vnd.ms-word.document.macroenabled.12 + dotm: + - application/vnd.ms-word.template.macroenabled.12 + wps: + - application/vnd.ms-works + wks: + - application/vnd.ms-works + wcm: + - application/vnd.ms-works + wdb: + - application/vnd.ms-works + wpl: + - application/vnd.ms-wpl + xps: + - application/vnd.ms-xpsdocument + mseq: + - application/vnd.mseq + mus: + - application/vnd.musician + msty: + - application/vnd.muvee.style + taglet: + - application/vnd.mynfc + nlu: + - application/vnd.neurolanguage.nlu + ntf: + - application/vnd.nitf + nitf: + - application/vnd.nitf + nnd: + - application/vnd.noblenet-directory + nns: + - application/vnd.noblenet-sealer + nnw: + - application/vnd.noblenet-web + ngdat: + - application/vnd.nokia.n-gage.data + n-gage: + - application/vnd.nokia.n-gage.symbian.install + rpst: + - application/vnd.nokia.radio-preset + rpss: + - application/vnd.nokia.radio-presets + edm: + - application/vnd.novadigm.edm + edx: + - application/vnd.novadigm.edx + ext: + - application/vnd.novadigm.ext + odc: + - application/vnd.oasis.opendocument.chart + otc: + - application/vnd.oasis.opendocument.chart-template + odb: + - application/vnd.oasis.opendocument.database + odf: + - application/vnd.oasis.opendocument.formula + odft: + - application/vnd.oasis.opendocument.formula-template + odg: + - application/vnd.oasis.opendocument.graphics + otg: + - application/vnd.oasis.opendocument.graphics-template + odi: + - application/vnd.oasis.opendocument.image + oti: + - application/vnd.oasis.opendocument.image-template + odp: + - application/vnd.oasis.opendocument.presentation + otp: + - application/vnd.oasis.opendocument.presentation-template + ods: + - application/vnd.oasis.opendocument.spreadsheet + ots: + - application/vnd.oasis.opendocument.spreadsheet-template + odt: + - application/vnd.oasis.opendocument.text + odm: + - application/vnd.oasis.opendocument.text-master + ott: + - application/vnd.oasis.opendocument.text-template + oth: + - application/vnd.oasis.opendocument.text-web + xo: + - application/vnd.olpc-sugar + dd2: + - application/vnd.oma.dd2+xml + oxt: + - application/vnd.openofficeorg.extension + pptx: + - application/vnd.openxmlformats-officedocument.presentationml.presentation + sldx: + - application/vnd.openxmlformats-officedocument.presentationml.slide + ppsx: + - application/vnd.openxmlformats-officedocument.presentationml.slideshow + potx: + - application/vnd.openxmlformats-officedocument.presentationml.template + xlsx: + - application/vnd.openxmlformats-officedocument.spreadsheetml.sheet + xltx: + - application/vnd.openxmlformats-officedocument.spreadsheetml.template + docx: + - application/vnd.openxmlformats-officedocument.wordprocessingml.document + dotx: + - application/vnd.openxmlformats-officedocument.wordprocessingml.template + mgp: + - application/vnd.osgeo.mapguide.package + dp: + - application/vnd.osgi.dp + esa: + - application/vnd.osgi.subsystem + pdb: + - application/vnd.palm + pqa: + - application/vnd.palm + oprc: + - application/vnd.palm + paw: + - application/vnd.pawaafile + str: + - application/vnd.pg.format + ei6: + - application/vnd.pg.osasli + efif: + - application/vnd.picsel + wg: + - application/vnd.pmi.widget + plf: + - application/vnd.pocketlearn + pbd: + - application/vnd.powerbuilder6 + box: + - application/vnd.previewsystems.box + mgz: + - application/vnd.proteus.magazine + qps: + - application/vnd.publishare-delta-tree + ptid: + - application/vnd.pvi.ptid1 + qxd: + - application/vnd.quark.quarkxpress + qxt: + - application/vnd.quark.quarkxpress + qwd: + - application/vnd.quark.quarkxpress + qwt: + - application/vnd.quark.quarkxpress + qxl: + - application/vnd.quark.quarkxpress + qxb: + - application/vnd.quark.quarkxpress + bed: + - application/vnd.realvnc.bed + mxl: + - application/vnd.recordare.musicxml + musicxml: + - application/vnd.recordare.musicxml+xml + cryptonote: + - application/vnd.rig.cryptonote + cod: + - application/vnd.rim.cod + rm: + - application/vnd.rn-realmedia + rmvb: + - application/vnd.rn-realmedia-vbr + link66: + - application/vnd.route66.link66+xml + st: + - application/vnd.sailingtracker.track + see: + - application/vnd.seemail + sema: + - application/vnd.sema + semd: + - application/vnd.semd + semf: + - application/vnd.semf + ifm: + - application/vnd.shana.informed.formdata + itp: + - application/vnd.shana.informed.formtemplate + iif: + - application/vnd.shana.informed.interchange + ipk: + - application/vnd.shana.informed.package + twd: + - application/vnd.simtech-mindmapper + twds: + - application/vnd.simtech-mindmapper + mmf: + - application/vnd.smaf + teacher: + - application/vnd.smart.teacher + sdkm: + - application/vnd.solent.sdkm+xml + sdkd: + - application/vnd.solent.sdkm+xml + dxp: + - application/vnd.spotfire.dxp + sfs: + - application/vnd.spotfire.sfs + sdc: + - application/vnd.stardivision.calc + sda: + - application/vnd.stardivision.draw + sdd: + - application/vnd.stardivision.impress + smf: + - application/vnd.stardivision.math + sdw: + - application/vnd.stardivision.writer + vor: + - application/vnd.stardivision.writer + sgl: + - application/vnd.stardivision.writer-global + smzip: + - application/vnd.stepmania.package + sm: + - application/vnd.stepmania.stepchart + sxc: + - application/vnd.sun.xml.calc + stc: + - application/vnd.sun.xml.calc.template + sxd: + - application/vnd.sun.xml.draw + std: + - application/vnd.sun.xml.draw.template + sxi: + - application/vnd.sun.xml.impress + sti: + - application/vnd.sun.xml.impress.template + sxm: + - application/vnd.sun.xml.math + sxw: + - application/vnd.sun.xml.writer + sxg: + - application/vnd.sun.xml.writer.global + stw: + - application/vnd.sun.xml.writer.template + sus: + - application/vnd.sus-calendar + susp: + - application/vnd.sus-calendar + svd: + - application/vnd.svd + sis: + - application/vnd.symbian.install + sisx: + - application/vnd.symbian.install + xsm: + - application/vnd.syncml+xml + bdm: + - application/vnd.syncml.dm+wbxml + xdm: + - application/vnd.syncml.dm+xml + tao: + - application/vnd.tao.intent-module-archive + pcap: + - application/vnd.tcpdump.pcap + cap: + - application/vnd.tcpdump.pcap + dmp: + - application/vnd.tcpdump.pcap + tmo: + - application/vnd.tmobile-livetv + tpt: + - application/vnd.trid.tpt + mxs: + - application/vnd.triscape.mxs + tra: + - application/vnd.trueapp + ufd: + - application/vnd.ufdl + ufdl: + - application/vnd.ufdl + utz: + - application/vnd.uiq.theme + umj: + - application/vnd.umajin + unityweb: + - application/vnd.unity + uoml: + - application/vnd.uoml+xml + vcx: + - application/vnd.vcx + vsd: + - application/vnd.visio + vst: + - application/vnd.visio + vss: + - application/vnd.visio + vsw: + - application/vnd.visio + vis: + - application/vnd.visionary + vsf: + - application/vnd.vsf + wbxml: + - application/vnd.wap.wbxml + wmlc: + - application/vnd.wap.wmlc + wmlsc: + - application/vnd.wap.wmlscriptc + wtb: + - application/vnd.webturbo + nbp: + - application/vnd.wolfram.player + wpd: + - application/vnd.wordperfect + wqd: + - application/vnd.wqd + stf: + - application/vnd.wt.stf + xar: + - application/vnd.xara + xfdl: + - application/vnd.xfdl + hvd: + - application/vnd.yamaha.hv-dic + hvs: + - application/vnd.yamaha.hv-script + hvp: + - application/vnd.yamaha.hv-voice + osf: + - application/vnd.yamaha.openscoreformat + osfpvg: + - application/vnd.yamaha.openscoreformat.osfpvg+xml + saf: + - application/vnd.yamaha.smaf-audio + spf: + - application/vnd.yamaha.smaf-phrase + cmp: + - application/vnd.yellowriver-custom-menu + zir: + - application/vnd.zul + zirz: + - application/vnd.zul + zaz: + - application/vnd.zzazz.deck+xml + vxml: + - application/voicexml+xml + wgt: + - application/widget + hlp: + - application/winhlp + wsdl: + - application/wsdl+xml + wspolicy: + - application/wspolicy+xml + 7z: + - application/x-7z-compressed + abw: + - application/x-abiword + ace: + - application/x-ace-compressed + dmg: + - application/x-apple-diskimage + aab: + - application/x-authorware-bin + x32: + - application/x-authorware-bin + u32: + - application/x-authorware-bin + vox: + - application/x-authorware-bin + aam: + - application/x-authorware-map + aas: + - application/x-authorware-seg + bcpio: + - application/x-bcpio + torrent: + - application/x-bittorrent + blb: + - application/x-blorb + blorb: + - application/x-blorb + bz: + - application/x-bzip + bz2: + - application/x-bzip2 + boz: + - application/x-bzip2 + cbr: + - application/x-cbr + cba: + - application/x-cbr + cbt: + - application/x-cbr + cbz: + - application/x-cbr + cb7: + - application/x-cbr + vcd: + - application/x-cdlink + cfs: + - application/x-cfs-compressed + chat: + - application/x-chat + pgn: + - application/x-chess-pgn + nsc: + - application/x-conference + cpio: + - application/x-cpio + csh: + - application/x-csh + deb: + - application/x-debian-package + udeb: + - application/x-debian-package + dgc: + - application/x-dgc-compressed + dir: + - application/x-director + dcr: + - application/x-director + dxr: + - application/x-director + cst: + - application/x-director + cct: + - application/x-director + cxt: + - application/x-director + w3d: + - application/x-director + fgd: + - application/x-director + swa: + - application/x-director + wad: + - application/x-doom + ncx: + - application/x-dtbncx+xml + dtb: + - application/x-dtbook+xml + res: + - application/x-dtbresource+xml + dvi: + - application/x-dvi + evy: + - application/x-envoy + eva: + - application/x-eva + bdf: + - application/x-font-bdf + gsf: + - application/x-font-ghostscript + psf: + - application/x-font-linux-psf + pcf: + - application/x-font-pcf + snf: + - application/x-font-snf + pfa: + - application/x-font-type1 + pfb: + - application/x-font-type1 + pfm: + - application/x-font-type1 + afm: + - application/x-font-type1 + arc: + - application/x-freearc + spl: + - application/x-futuresplash + gca: + - application/x-gca-compressed + ulx: + - application/x-glulx + gnumeric: + - application/x-gnumeric + gramps: + - application/x-gramps-xml + gtar: + - application/x-gtar + hdf: + - application/x-hdf + install: + - application/x-install-instructions + iso: + - application/x-iso9660-image + jnlp: + - application/x-java-jnlp-file + latex: + - application/x-latex + lzh: + - application/x-lzh-compressed + lha: + - application/x-lzh-compressed + mie: + - application/x-mie + prc: + - application/x-mobipocket-ebook + mobi: + - application/x-mobipocket-ebook + application: + - application/x-ms-application + lnk: + - application/x-ms-shortcut + wmd: + - application/x-ms-wmd + wmz: + - application/x-ms-wmz + - application/x-msmetafile + xbap: + - application/x-ms-xbap + mdb: + - application/x-msaccess + obd: + - application/x-msbinder + crd: + - application/x-mscardfile + clp: + - application/x-msclip + exe: + - application/x-msdownload + dll: + - application/x-msdownload + com: + - application/x-msdownload + bat: + - application/x-msdownload + msi: + - application/x-msdownload + mvb: + - application/x-msmediaview + m13: + - application/x-msmediaview + m14: + - application/x-msmediaview + wmf: + - application/x-msmetafile + emf: + - application/x-msmetafile + emz: + - application/x-msmetafile + mny: + - application/x-msmoney + pub: + - application/x-mspublisher + scd: + - application/x-msschedule + trm: + - application/x-msterminal + wri: + - application/x-mswrite + nc: + - application/x-netcdf + cdf: + - application/x-netcdf + nzb: + - application/x-nzb + p12: + - application/x-pkcs12 + pfx: + - application/x-pkcs12 + p7b: + - application/x-pkcs7-certificates + spc: + - application/x-pkcs7-certificates + p7r: + - application/x-pkcs7-certreqresp + rar: + - application/x-rar-compressed + ris: + - application/x-research-info-systems + sh: + - application/x-sh + shar: + - application/x-shar + swf: + - application/x-shockwave-flash + xap: + - application/x-silverlight-app + sql: + - application/x-sql + sit: + - application/x-stuffit + sitx: + - application/x-stuffitx + srt: + - application/x-subrip + sv4cpio: + - application/x-sv4cpio + sv4crc: + - application/x-sv4crc + t3: + - application/x-t3vm-image + gam: + - application/x-tads + tar: + - application/x-tar + tcl: + - application/x-tcl + tex: + - application/x-tex + tfm: + - application/x-tex-tfm + texinfo: + - application/x-texinfo + texi: + - application/x-texinfo + obj: + - application/x-tgif + ustar: + - application/x-ustar + src: + - application/x-wais-source + der: + - application/x-x509-ca-cert + crt: + - application/x-x509-ca-cert + fig: + - application/x-xfig + xlf: + - application/x-xliff+xml + xpi: + - application/x-xpinstall + xz: + - application/x-xz + z1: + - application/x-zmachine + z2: + - application/x-zmachine + z3: + - application/x-zmachine + z4: + - application/x-zmachine + z5: + - application/x-zmachine + z6: + - application/x-zmachine + z7: + - application/x-zmachine + z8: + - application/x-zmachine + xaml: + - application/xaml+xml + xdf: + - application/xcap-diff+xml + xenc: + - application/xenc+xml + xhtml: + - application/xhtml+xml + xht: + - application/xhtml+xml + xml: + - application/xml + xsl: + - application/xml + dtd: + - application/xml-dtd + xop: + - application/xop+xml + xpl: + - application/xproc+xml + xslt: + - application/xslt+xml + xspf: + - application/xspf+xml + mxml: + - application/xv+xml + xhvml: + - application/xv+xml + xvml: + - application/xv+xml + xvm: + - application/xv+xml + yang: + - application/yang + yin: + - application/yin+xml + adp: + - audio/adpcm + au: + - audio/basic + snd: + - audio/basic + mid: + - audio/midi + midi: + - audio/midi + kar: + - audio/midi + rmi: + - audio/midi + m4a: + - audio/mp4 + mp4a: + - audio/mp4 + oga: + - audio/ogg + ogg: + - audio/ogg + spx: + - audio/ogg + s3m: + - audio/s3m + sil: + - audio/silk + uva: + - audio/vnd.dece.audio + uvva: + - audio/vnd.dece.audio + eol: + - audio/vnd.digital-winds + dra: + - audio/vnd.dra + dts: + - audio/vnd.dts + dtshd: + - audio/vnd.dts.hd + lvp: + - audio/vnd.lucent.voice + pya: + - audio/vnd.ms-playready.media.pya + ecelp4800: + - audio/vnd.nuera.ecelp4800 + ecelp7470: + - audio/vnd.nuera.ecelp7470 + ecelp9600: + - audio/vnd.nuera.ecelp9600 + rip: + - audio/vnd.rip + weba: + - audio/webm + aac: + - audio/x-aac + aif: + - audio/x-aiff + aiff: + - audio/x-aiff + aifc: + - audio/x-aiff + caf: + - audio/x-caf + flac: + - audio/x-flac + mka: + - audio/x-matroska + m3u: + - audio/x-mpegurl + wax: + - audio/x-ms-wax + wma: + - audio/x-ms-wma + ram: + - audio/x-pn-realaudio + ra: + - audio/x-pn-realaudio + rmp: + - audio/x-pn-realaudio-plugin + wav: + - audio/x-wav + xm: + - audio/xm + cdx: + - chemical/x-cdx + cif: + - chemical/x-cif + cmdf: + - chemical/x-cmdf + cml: + - chemical/x-cml + csml: + - chemical/x-csml + xyz: + - chemical/x-xyz + woff: + - font/woff + woff2: + - font/woff2 + cgm: + - image/cgm + g3: + - image/g3fax + gif: + - image/gif + ief: + - image/ief + ktx: + - image/ktx + png: + - image/png + btif: + - image/prs.btif + sgi: + - image/sgi + svg: + - image/svg+xml + svgz: + - image/svg+xml + tiff: + - image/tiff + tif: + - image/tiff + psd: + - image/vnd.adobe.photoshop + uvi: + - image/vnd.dece.graphic + uvvi: + - image/vnd.dece.graphic + uvg: + - image/vnd.dece.graphic + uvvg: + - image/vnd.dece.graphic + djvu: + - image/vnd.djvu + djv: + - image/vnd.djvu + sub: + - image/vnd.dvb.subtitle + - text/vnd.dvb.subtitle + dwg: + - image/vnd.dwg + dxf: + - image/vnd.dxf + fbs: + - image/vnd.fastbidsheet + fpx: + - image/vnd.fpx + fst: + - image/vnd.fst + mmr: + - image/vnd.fujixerox.edmics-mmr + rlc: + - image/vnd.fujixerox.edmics-rlc + mdi: + - image/vnd.ms-modi + wdp: + - image/vnd.ms-photo + npx: + - image/vnd.net-fpx + wbmp: + - image/vnd.wap.wbmp + xif: + - image/vnd.xiff + webp: + - image/webp + 3ds: + - image/x-3ds + ras: + - image/x-cmu-raster + cmx: + - image/x-cmx + fh: + - image/x-freehand + fhc: + - image/x-freehand + fh4: + - image/x-freehand + fh5: + - image/x-freehand + fh7: + - image/x-freehand + ico: + - image/x-icon + sid: + - image/x-mrsid-image + pcx: + - image/x-pcx + pic: + - image/x-pict + pct: + - image/x-pict + pnm: + - image/x-portable-anymap + pbm: + - image/x-portable-bitmap + pgm: + - image/x-portable-graymap + ppm: + - image/x-portable-pixmap + rgb: + - image/x-rgb + tga: + - image/x-tga + xbm: + - image/x-xbitmap + xpm: + - image/x-xpixmap + xwd: + - image/x-xwindowdump + eml: + - message/rfc822 + mime: + - message/rfc822 + igs: + - model/iges + iges: + - model/iges + msh: + - model/mesh + mesh: + - model/mesh + silo: + - model/mesh + dae: + - model/vnd.collada+xml + dwf: + - model/vnd.dwf + gdl: + - model/vnd.gdl + gtw: + - model/vnd.gtw + mts: + - model/vnd.mts + vtu: + - model/vnd.vtu + wrl: + - model/vrml + vrml: + - model/vrml + x3db: + - model/x3d+binary + x3dbz: + - model/x3d+binary + x3dv: + - model/x3d+vrml + x3dvz: + - model/x3d+vrml + x3d: + - model/x3d+xml + x3dz: + - model/x3d+xml + appcache: + - text/cache-manifest + ics: + - text/calendar + ifb: + - text/calendar + css: + - text/css + csv: + - text/csv + html: + - text/html + htm: + - text/html + n3: + - text/n3 + txt: + - text/plain + text: + - text/plain + conf: + - text/plain + def: + - text/plain + list: + - text/plain + log: + - text/plain + in: + - text/plain + dsc: + - text/prs.lines.tag + rtx: + - text/richtext + sgml: + - text/sgml + sgm: + - text/sgml + tsv: + - text/tab-separated-values + t: + - text/troff + tr: + - text/troff + roff: + - text/troff + man: + - text/troff + me: + - text/troff + ms: + - text/troff + ttl: + - text/turtle + uri: + - text/uri-list + uris: + - text/uri-list + urls: + - text/uri-list + vcard: + - text/vcard + curl: + - text/vnd.curl + dcurl: + - text/vnd.curl.dcurl + mcurl: + - text/vnd.curl.mcurl + scurl: + - text/vnd.curl.scurl + fly: + - text/vnd.fly + flx: + - text/vnd.fmi.flexstor + gv: + - text/vnd.graphviz + 3dml: + - text/vnd.in3d.3dml + spot: + - text/vnd.in3d.spot + jad: + - text/vnd.sun.j2me.app-descriptor + wml: + - text/vnd.wap.wml + wmls: + - text/vnd.wap.wmlscript + s: + - text/x-asm + asm: + - text/x-asm + c: + - text/x-c + cc: + - text/x-c + cxx: + - text/x-c + cpp: + - text/x-c + h: + - text/x-c + hh: + - text/x-c + dic: + - text/x-c + f: + - text/x-fortran + for: + - text/x-fortran + f77: + - text/x-fortran + f90: + - text/x-fortran + java: + - text/x-java-source + nfo: + - text/x-nfo + opml: + - text/x-opml + p: + - text/x-pascal + pas: + - text/x-pascal + etx: + - text/x-setext + sfv: + - text/x-sfv + uu: + - text/x-uuencode + vcs: + - text/x-vcalendar + vcf: + - text/x-vcard + 3gp: + - video/3gpp + 3g2: + - video/3gpp2 + h261: + - video/h261 + h263: + - video/h263 + h264: + - video/h264 + jpgv: + - video/jpeg + jpm: + - video/jpm + jpgm: + - video/jpm + mj2: + - video/mj2 + mjp2: + - video/mj2 + mp4: + - video/mp4 + mp4v: + - video/mp4 + mpg4: + - video/mp4 + mpeg: + - video/mpeg + mpg: + - video/mpeg + mpe: + - video/mpeg + m1v: + - video/mpeg + m2v: + - video/mpeg + ogv: + - video/ogg + qt: + - video/quicktime + mov: + - video/quicktime + uvh: + - video/vnd.dece.hd + uvvh: + - video/vnd.dece.hd + uvm: + - video/vnd.dece.mobile + uvvm: + - video/vnd.dece.mobile + uvp: + - video/vnd.dece.pd + uvvp: + - video/vnd.dece.pd + uvs: + - video/vnd.dece.sd + uvvs: + - video/vnd.dece.sd + uvv: + - video/vnd.dece.video + uvvv: + - video/vnd.dece.video + dvb: + - video/vnd.dvb.file + fvt: + - video/vnd.fvt + mxu: + - video/vnd.mpegurl + m4u: + - video/vnd.mpegurl + pyv: + - video/vnd.ms-playready.media.pyv + uvu: + - video/vnd.uvvu.mp4 + uvvu: + - video/vnd.uvvu.mp4 + viv: + - video/vnd.vivo + webm: + - video/webm + f4v: + - video/x-f4v + fli: + - video/x-fli + flv: + - video/x-flv + m4v: + - video/x-m4v + mkv: + - video/x-matroska + mk3d: + - video/x-matroska + mks: + - video/x-matroska + mng: + - video/x-mng + asf: + - video/x-ms-asf + asx: + - video/x-ms-asf + vob: + - video/x-ms-vob + wm: + - video/x-ms-wm + wmv: + - video/x-ms-wmv + wmx: + - video/x-ms-wmx + wvx: + - video/x-ms-wvx + avi: + - video/x-msvideo + movie: + - video/x-sgi-movie + smv: + - video/x-smv + ice: + - x-conference/x-cooltalk diff --git a/system/config/permissions.yaml b/system/config/permissions.yaml new file mode 100644 index 0000000..a65b223 --- /dev/null +++ b/system/config/permissions.yaml @@ -0,0 +1,53 @@ +actions: + site: + type: access + label: Site + admin: + type: access + label: Admin + admin.pages: + type: access + label: Pages + admin.users: + type: access + label: User Accounts + +types: + default: + type: access + + crud: + type: compact + letters: + c: + action: create + label: PLUGIN_ADMIN.CREATE + r: + action: read + label: PLUGIN_ADMIN.READ + u: + action: update + label: PLUGIN_ADMIN.UPDATE + d: + action: delete + label: PLUGIN_ADMIN.DELETE + + crudp: + type: crud + letters: + p: + action: publish + label: PLUGIN_ADMIN.PUBLISH + + crudl: + type: crud + letters: + l: + action: list + label: PLUGIN_ADMIN.LIST + + crudpl: + type: crud + use: + - crudp + - crudl diff --git a/system/config/scheduler.yaml b/system/config/scheduler.yaml new file mode 100644 index 0000000..8868532 --- /dev/null +++ b/system/config/scheduler.yaml @@ -0,0 +1,68 @@ +# Grav Scheduler Configuration + +# Default scheduler settings (backward compatible) +defaults: + output: true + output_type: file + email: null + +# Status of individual jobs (enabled/disabled) +status: {} + +# Custom scheduled jobs +custom_jobs: {} + +# Modern scheduler features (disabled by default for backward compatibility) +modern: + # Enable modern scheduler features + enabled: false + + # Number of concurrent workers (1 = sequential execution like legacy) + workers: 1 + + # Job retry configuration + retry: + enabled: true + max_attempts: 3 + backoff: exponential # 'linear' or 'exponential' + + # Job queue configuration + queue: + path: user-data://scheduler/queue + max_size: 1000 + + # Webhook trigger configuration + webhook: + enabled: false + token: null # Set a secure token to enable webhook triggers + path: /scheduler/webhook + + # Health check endpoint + health: + enabled: true + path: /scheduler/health + + # Job execution history + history: + enabled: true + retention_days: 30 + path: user-data://scheduler/history + + # Performance settings + performance: + job_timeout: 300 # Default timeout in seconds + lock_timeout: 10 # Lock acquisition timeout in seconds + + # Monitoring and alerts + monitoring: + enabled: false + alert_on_failure: true + alert_email: null + webhook_url: null + + # Trigger detection methods + triggers: + check_cron: true + check_systemd: true + check_webhook: true + check_external: true \ No newline at end of file diff --git a/system/config/security.yaml b/system/config/security.yaml new file mode 100644 index 0000000..43d3132 --- /dev/null +++ b/system/config/security.yaml @@ -0,0 +1,47 @@ +xss_whitelist: [admin.super] # Whitelist of user access that should 'skip' XSS checking +xss_enabled: + on_events: true + invalid_protocols: true + moz_binding: true + html_inline_styles: true + dangerous_tags: true +xss_invalid_protocols: + - javascript + - livescript + - vbscript + - mocha + - feed + - data +xss_dangerous_tags: + - applet + - meta + - xml + - blink + - link + - style + - script + - embed + - object + - iframe + - frame + - frameset + - ilayer + - layer + - bgsound + - title + - base +uploads_dangerous_extensions: + - php + - php2 + - php3 + - php4 + - php5 + - phar + - phtml + - html + - htm + - shtml + - shtm + - js + - exe +sanitize_svg: true diff --git a/system/config/site.yaml b/system/config/site.yaml new file mode 100644 index 0000000..f46cca4 --- /dev/null +++ b/system/config/site.yaml @@ -0,0 +1,35 @@ +title: Grav # Name of the site +default_lang: en # Default language for site (potentially used by theme) + +author: + name: John Appleseed # Default author name + email: 'john@example.com' # Default author email + +taxonomies: [category,tag] # Arbitrary list of taxonomy types + +metadata: + description: 'My Grav Site' # Site description + +summary: + enabled: true # enable or disable summary of page + format: short # long = summary delimiter will be ignored; short = use the first occurrence of delimiter or size + size: 300 # Maximum length of summary (characters) + delimiter: === # The summary delimiter + +redirects: +# '/redirect-test': '/' # Redirect test goes to home page +# '/old/(.*)': '/new/$1' # Would redirect /old/my-page to /new/my-page + +routes: +# '/something/else': '/blog/sample-3' # Alias for /blog/sample-3 +# '/new/(.*)': '/blog/$1' # Regex any /new/my-page URL to /blog/my-page Route + +blog: + route: '/blog' # Custom value added (accessible via site.blog.route) + +#menu: # Menu Example +# - text: Source +# icon: github +# url: https://github.com/getgrav/grav +# - icon: twitter +# url: http://twitter.com/getgrav diff --git a/system/config/system.yaml b/system/config/system.yaml new file mode 100644 index 0000000..984f467 --- /dev/null +++ b/system/config/system.yaml @@ -0,0 +1,235 @@ +absolute_urls: false # Absolute or relative URLs for `base_url` +timezone: '' # Valid values: http://php.net/manual/en/timezones.php +default_locale: # Default locale (defaults to system) +param_sep: ':' # Parameter separator, use ';' for Apache on windows +wrapped_site: false # For themes/plugins to know if Grav is wrapped by another platform +reverse_proxy_setup: false # Running in a reverse proxy scenario with different webserver ports than proxy +force_ssl: false # If enabled, Grav forces to be accessed via HTTPS (NOTE: Not an ideal solution) +force_lowercase_urls: true # If you want to support mixed cased URLs set this to false +custom_base_url: '' # Set the base_url manually, e.g. http://yoursite.com/yourpath +username_regex: '^[a-z0-9_-]{3,16}$' # Only lowercase chars, digits, dashes, underscores. 3 - 16 chars +pwd_regex: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}' # At least one number, one uppercase and lowercase letter, and be at least 8+ chars +intl_enabled: true # Special logic for PHP International Extension (mod_intl) +http_x_forwarded: # Configuration options for the various HTTP_X_FORWARD headers + protocol: true + host: false + port: true + ip: true + +languages: + supported: [] # List of languages supported. eg: [en, fr, de] + default_lang: # Default is the first supported language. Must be one of the supported languages + include_default_lang: true # Include the default lang prefix in all URLs + include_default_lang_file_extension: true # If true, include language code for the default language in file extension: default.en.md + translations: true # If false, translation keys are used instead of translated strings + translations_fallback: true # Fallback through supported translations if active lang doesn't exist + session_store_active: false # Store active language in session + http_accept_language: false # Attempt to set the language based on http_accept_language header in the browser + override_locale: false # Override the default or system locale with language specific one + content_fallback: {} # Custom language fallbacks. eg: {fr: ['fr', 'en']} + pages_fallback_only: false # DEPRECATED: Use `content_fallback` instead + debug: false # Debug language detection + +home: + alias: '/home' # Default path for home, ie / + hide_in_urls: false # Hide the home route in URLs + +pages: + type: regular # EXPERIMENTAL: Page type: regular or flex + dirs: ['page://'] # Advanced functionality, allows for multiple page paths + theme: quark # Default theme (defaults to "quark" theme) + order: + by: default # Order pages by "default", "alpha" or "date" + dir: asc # Default ordering direction, "asc" or "desc" + list: + count: 20 # Default item count per page + dateformat: + default: # The default date format Grav expects in the `date: ` field + short: 'jS M Y' # Short date format + long: 'F jS \a\t g:ia' # Long date format + publish_dates: true # automatically publish/unpublish based on dates + process: + markdown: true # Process Markdown + twig: false # Process Twig + twig_first: false # Process Twig before markdown when processing both on a page + never_cache_twig: false # Only cache content, never cache twig processed in content (incompatible with `twig_first: true`) + events: + page: true # Enable page level events + twig: true # Enable Twig level events + markdown: + extra: false # Enable support for Markdown Extra support (GFM by default) + auto_line_breaks: false # Enable automatic line breaks + auto_url_links: false # Enable automatic HTML links + escape_markup: false # Escape markup tags into entities + special_chars: # List of special characters to automatically convert to entities + '>': 'gt' + '<': 'lt' + valid_link_attributes: # Valid attributes to pass through via markdown links + - rel + - target + - id + - class + - classes + types: [html,htm,xml,txt,json,rss,atom] # list of valid page types + append_url_extension: '' # Append page's extension in Page urls (e.g. '.html' results in /path/page.html) + expires: 604800 # Page expires time in seconds (604800 seconds = 7 days) + cache_control: # Can be blank for no setting, or a valid `cache-control` text value + last_modified: false # Set the last modified date header based on file modification timestamp + etag: true # Set the etag header tag + vary_accept_encoding: false # Add `Vary: Accept-Encoding` header + redirect_default_code: 302 # Default code to use for redirects: 301|302|303 + redirect_trailing_slash: 1 # Always redirect trailing slash with redirect code 0|1|301|302 (0: no redirect, 1: use default code) + redirect_default_route: 0 # Always redirect to page's default route using code 0|1|301|302, also removes .htm and .html extensions + ignore_files: [.DS_Store] # Files to ignore in Pages + ignore_folders: [.git, .idea] # Folders to ignore in Pages + ignore_hidden: true # Ignore all Hidden files and folders + hide_empty_folders: false # If folder has no .md file, should it be hidden + url_taxonomy_filters: true # Enable auto-magic URL-based taxonomy filters for page collections + frontmatter: + process_twig: false # Should the frontmatter be processed to replace Twig variables? + ignore_fields: ['form','forms'] # Fields that might contain Twig variables and should not be processed + +cache: + enabled: true # Set to true to enable caching + check: + method: file # Method to check for updates in pages: file|folder|hash|none + driver: auto # One of: auto|file|apcu|memcache|wincache + prefix: 'g' # Cache prefix string (prevents cache conflicts) + purge_at: '0 4 * * *' # How often to purge old file cache (using new scheduler) + clear_at: '0 3 * * *' # How often to clear cache (using new scheduler) + clear_job_type: 'standard' # Type to clear when processing the scheduled clear job `standard`|`all` + clear_images_by_default: false # By default grav does not include processed images in cache clear, this can be enabled + cli_compatibility: false # Ensures only non-volatile drivers are used (file, redis, memcache, etc.) + lifetime: 604800 # Lifetime of cached data in seconds (0 = infinite) + purge_max_age_days: 30 # Maximum age of cache items in days before they are purged + gzip: false # GZip compress the page output + allow_webserver_gzip: false # If true, `content-encoding: identity` but connection isn't closed before `onShutDown()` event + redis: + socket: false # Path to redis unix socket (e.g. /var/run/redis/redis.sock), false = use server and port to connect + password: # Optional password + database: # Optional database ID + +twig: + cache: true # Set to true to enable Twig caching + debug: true # Enable Twig debug + auto_reload: true # Refresh cache on changes + autoescape: true # Autoescape Twig vars (DEPRECATED, always enabled in strict mode) + undefined_functions: true # Allow undefined functions + undefined_filters: true # Allow undefined filters + safe_functions: [] # List of PHP functions which are allowed to be used as Twig functions + safe_filters: [] # List of PHP functions which are allowed to be used as Twig filters + umask_fix: false # By default Twig creates cached files as 755, fix switches this to 775 + +assets: # Configuration for Assets Manager (JS, CSS) + css_pipeline: false # The CSS pipeline is the unification of multiple CSS resources into one file + css_pipeline_include_externals: true # Include external URLs in the pipeline by default + css_pipeline_before_excludes: true # Render the pipeline before any excluded files + css_minify: true # Minify the CSS during pipelining + css_minify_windows: false # Minify Override for Windows platforms. False by default due to ThreadStackSize + css_rewrite: true # Rewrite any CSS relative URLs during pipelining + js_pipeline: false # The JS pipeline is the unification of multiple JS resources into one file + js_pipeline_include_externals: true # Include external URLs in the pipeline by default + js_pipeline_before_excludes: true # Render the pipeline before any excluded files + js_module_pipeline: false # The JS Module pipeline is the unification of multiple JS Module resources into one file + js_module_pipeline_include_externals: true # Include external URLs in the pipeline by default + js_module_pipeline_before_excludes: true # Render the pipeline before any excluded files + js_minify: true # Minify the JS during pipelining + enable_asset_timestamp: false # Enable asset timestamps + enable_asset_sri: false # Enable asset SRI + collections: + jquery: system://assets/jquery/jquery-3.x.min.js + +errors: + display: 0 # Display either (1) Full backtrace | (0) Simple Error | (-1) System Error + log: true # Log errors to /logs folder + +log: + handler: file # Log handler. Currently supported: file | syslog + syslog: + facility: local6 # Syslog facilities output + tag: grav # Syslog tag. Default: "grav". + +debugger: + enabled: false # Enable Grav debugger and following settings + provider: clockwork # Debugger provider: debugbar | clockwork + censored: false # Censor potentially sensitive information (POST parameters, cookies, files, configuration and most array/object data in log messages) + shutdown: + close_connection: true # Close the connection before calling onShutdown(). false for debugging + +images: + adapter: gd # Image adapter to use: gd | imagick + default_image_quality: 85 # Default image quality to use when resampling images (85%) + cache_all: false # Cache all image by default + cache_perms: '0755' # MUST BE IN QUOTES!! Default cache folder perms. Usually '0755' or '0775' + debug: false # Show an overlay over images indicating the pixel depth of the image when working with retina for example + auto_fix_orientation: true # Automatically fix the image orientation based on the Exif data + seofriendly: false # SEO-friendly processed image names + cls: # Cumulative Layout Shift: See https://web.dev/optimize-cls/ + auto_sizes: false # Automatically add height/width to image + aspect_ratio: false # Reserve space with aspect ratio style + retina_scale: 1 # scale to adjust auto-sizes for better handling of HiDPI resolutions + defaults: + loading: auto # Let browser pick [auto|lazy|eager] + decoding: auto # Let browser pick [auto|sync|async] + fetchpriority: auto # Let browser pick [auto|high|low] + watermark: + image: 'system://images/watermark.png' # Path to a watermark image + position_y: 'center' # top|center|bottom + position_x: 'center' # left|center|right + scale: 33 # percentage of watermark scale + watermark_all: false # automatically watermark all images + +media: + enable_media_timestamp: false # Enable media timestamps + unsupported_inline_types: [] # Array of supported media types to try to display inline + allowed_fallback_types: [] # Array of allowed media types of files found if accessed via Page route + auto_metadata_exif: false # Automatically create metadata files from Exif data where possible + +session: + enabled: true # Enable Session support + initialize: true # Initialize session from Grav (if false, plugin needs to start the session) + timeout: 1800 # Timeout in seconds + name: grav-site # Name prefix of the session cookie. Use alphanumeric, dashes or underscores only. Do not use dots in the session name + uniqueness: path # Should sessions be `path` based or `security.salt` based + secure: false # Set session secure. If true, indicates that communication for this cookie must be over an encrypted transmission. Enable this only on sites that run exclusively on HTTPS + secure_https: true # Set session secure on HTTPS but not on HTTP. Has no effect if you have `session.secure: true`. Set to false if your site jumps between HTTP and HTTPS. + httponly: true # Set session HTTP only. If true, indicates that cookies should be used only over HTTP, and JavaScript modification is not allowed. + samesite: Lax # Set session SameSite. Possible values are Lax, Strict and None. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite + split: true # Sessions should be independent between site and plugins (such as admin) + domain: # Domain used by sessions. + path: # Path used by sessions. + +gpm: + releases: stable # Set to either 'stable' or 'testing' + official_gpm_only: true # By default GPM direct-install will only allow URLs via the official GPM proxy to ensure security + +http: + method: auto # Either 'curl', 'fopen' or 'auto'. 'auto' will try fopen first and if not available cURL + enable_proxy: true # Enable proxy server configuration + proxy_url: # Configure a manual proxy URL for GPM (eg 127.0.0.1:3128) + proxy_cert_path: # Local path to proxy certificate folder containing pem files + concurrent_connections: 5 # Concurrent HTTP connections when multiplexing + verify_peer: true # Enable/Disable SSL verification of peer certificates + verify_host: true # Enable/Disable SSL verification of host certificates + +accounts: + type: regular # EXPERIMENTAL: Account type: regular or flex + storage: file # EXPERIMENTAL: Flex storage type: file or folder + avatar: gravatar # Avatar generator [multiavatar|gravatar] + +flex: + cache: + index: + enabled: true # Set to true to enable Flex index caching. Is used to cache timestamps in files + lifetime: 60 # Lifetime of cached index in seconds (0 = infinite) + object: + enabled: true # Set to true to enable Flex object caching. Is used to cache object data + lifetime: 600 # Lifetime of cached objects in seconds (0 = infinite) + render: + enabled: true # Set to true to enable Flex render caching. Is used to cache rendered output + lifetime: 600 # Lifetime of cached HTML in seconds (0 = infinite) + +strict_mode: + yaml_compat: false # Set to true to enable YAML backwards compatibility + twig_compat: false # Set to true to enable deprecated Twig settings (autoescape: false) + blueprint_compat: false # Set to true to enable backward compatible strict support for blueprints diff --git a/system/defines.php b/system/defines.php new file mode 100644 index 0000000..7c1e890 --- /dev/null +++ b/system/defines.php @@ -0,0 +1,104 @@ +H`OJ@+rq56}IxXG~<)ZCfWHAt7UCYIIIQ z;uqFGv-Y>2PJwF;`IA<8f|j6!#OF9^+;#O&{Y&6ElamtJjoT+BB!0bc*5bUeNg)K| z;!nL8vjG_r5_J=}Uo|Wiiy3vugGK}=#gohA>kgD4#b!lNau+H@$OV{0a4~P55FsQ= zh;2)u%2C|B2qH-_DHe|}k<%-rWihmblBR`F@er2J$J%EIIv44gBdm%c!6KQ#!z_xS z(?yWmH`wz#>{|`vgm?}j(|Rz6GPrgO_o#qVyU`U){Jaf4UkW9(qh-VRn@;p|KX&CC z{IM5XM#-irqI?8DQw(MDuw{%qR|3sKBk$gLkpVh4J?5*`u})+JC*9~RSyylO}9)+4zCSk);0z8eewfs_p4eGA0fPLxrP z^e+%&OJw~7-Zo8q8^LeZzy*U?-#k$|jK6G0AGe^b(?s_ik<*U_)gtZ{@Q8@4pTv_o z(L41>*DUe86%DL`^Z3}Xh{)n$VH_mk1NEeYgp{rT|D^M{X*L7+P$qc^dk>9T1%Oie78)+Bz`F900HUYA}2 z_Wf3Q>^-Z&q*qU`g)6B1&|=a(X%7L-k~h;{i8uhoa!QX$Ym6xwy*P|WwnPRU@4+6c zq>@(0d4bj;vEb4jc@o@~*p#n^fMc+CQ$o?2miOKR;D*Aa8HGd`$PgJ6j0-L?CQ|a) z;L@>JPt01xz&kH|!S*HjQeBqOcIChmLZe1-Y5zPR6YF_YUkEmCiCLa9h`?SRtyP*X z?V|{WtP5lg+12mg8+0R|&Ie+qvYLVu&qS~y?{{qbtQ*?zUex%i52RnCpY3in2rl&a z(x48lPk!ab=I>JDpo5OyJ~8M+2Z4&9*w8$ql9$zUgtKM(%dOo)a80$NB^8~eTq1!`qNW>sntq_5&ngL=q8dU$ zY^o3(kXL0sU!8PwCM!&G?-3Kv+R!yEl7`T7w628>s|uLbrYOGLdTG!?0c>|%47HCr z$TZlq=s%IIEbB}-Id5VZY`K*7V!!mm5PUTvX@7ku~ z1&KNnaaeV1`CBDWK?z*ZW#Gdbg(KM56M2;lQ;Uy_t|(>c6*fX1im9EtxK1a*9C zr+f(O+VHr|uusxj=OD}Xnx=K%i{^&dx?u5NfY?v6Xg{4@S!N%p;|*%W8FcPFGNMZ5 z0o-o+$skEwG~LY>M)?JTk{24AoEKG>G7|5OBY-HX*2lXK5oT))>`PVdVw zvDPvt!}_=Tim_v&)dL^ zGglR>;=^)x7_!RmOPk0zwYvVt%4~FFWo3X@?aP@cCC@*yIC4faffJA4nXh!KslL}{ zB6VfDuYepSU*lU_qdxLuq%yrvJLOVLEIe7`>9dPRM^-sZlVj_{S>a!|@4hBWcBh=9 z7{#&&P`PafIckuD%nBef`tBqG%;-^!dgYA=2F7(VcEkYvHF|-n2PKQB8a`sGZspfq zKO%RyN%|!RZPc5+9H{_4lr1Y(4PoMaGOrp%^RY1z@D^=4ZBfCu}IE^1|nw`LxPRAas+f4Z#9?pE>sOYfMF5GMeKx)Fg zZD)^z&Y8ih5Q|;<%=p(~Z?#8MTABj?j9@T};@*AP_!?{23(sd6@qM=Q z54H+Ezi3b|{-(*<6CSti*$fa65I8=wu&E}YC2e^15B?3GmPWgBz%$duH!VcXz|=Ky zLj#4CqZ=?r6LNJy4V4V8Ut`))X75#*0PP{L{fLO?x*?&W@fsFR8v|0=07U)eW?kuXD&!PD?MX6KM03FSAU9i2i*(f#t z&?@UUh|gKAwelx8Ut(-a0m+8&J=UMrw(8gTGBnid_j2z0$R9M#2=IP9Ij*N@V0lc_T|LXYDtq&Ez_Q4+Y1?gPoBRp%Way_mqse8X8YCH@qvOKl$6D znt#-dIWOy;Rn`G>?vb>pJwa4aT}{^=ejLH5hv!N${}jfd2Ua7sZCi}gUyYzUt(`>a z9$`&Dq76IqA+37?5U+PeQcv5m-cde(%Fns2IY4hM{#sjZps&A}QkI~6NS{eN>^G{? zrr^#QUXJ=7S|T*9 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-3ds.png b/system/images/media/thumb-3ds.png new file mode 100644 index 0000000000000000000000000000000000000000..3161c699d0087f20f5598e790fddede47b5d7cc7 GIT binary patch literal 3116 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 Jxd9xM_&*EafxrL& literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-aac.png b/system/images/media/thumb-aac.png new file mode 100644 index 0000000000000000000000000000000000000000..62b035c68a6486397aa20b9f93ec70c0405a0004 GIT binary patch literal 3218 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 Zo|rJcCd*~<7XDww*x`zn*{&> literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-ai.png b/system/images/media/thumb-ai.png new file mode 100644 index 0000000000000000000000000000000000000000..21a647b7ecfb4f555fcf1a6808714bfe7409c359 GIT binary patch literal 1595 zcmbtT`8(8k9G_a+OwvCdiGD)_w&4;*YP~BpWd%`pqZ)Rdiqv6jYeB<#L|aov{k$x zyY{CQr@*d`TmdO(rU{cq%L-kG+exqJRi2RH30g+0%J>TZWM*t(07sujGX3CaV+uo% zqNhlnKO6^96on6eK7!z{gI|&vk0;f=z!ZA4Z3`LP>MKnc{SZ5KOcpjv2;P`V` zM*%+6gyeF`#VM>kkC5~d;{*6WJxIL_$C#p3@#OLxl6{vV3HXHtlJkK0K7!=^gSF*? zL^d(j567P+=O@7{8>}@8$+$(~C?V>^zqBHSPl(B3v^pN}9nrcZ5?%mrT*>8GIKc|< ztp;fxcvl%d)J!hUprv8>XCW~qf@96_-YUGW25WgwObn58HsXD?;GHKS z>4OvgM)Dt#Gh*=W23i@5mOjHiW+U%?@%}m>xP}VDk?i{**@0Y|MJr;^+9a&A5UqU$ zURoph0a$A$QWyx{UdNi#2yqYIQ;8G@BgG+vxErK7hZF>&wJ*uVX}r6foc@N^Bw{TY;FT?sbr?43M*9AjJtEXdstnu!d9; zp2tVpK$0DJeFbeu1?e{d{}Ngrg_cL6^?dk+Iq|ifoEas)ea6~ykoP_y*&ZZaY(~V2 zE4P<6(q~$D_e~8*EEO!3D(oC9T{3B#Dju!2pE;7R#QyYVTnG>O=?Y;RvUiughj+lX zi+bj!tv(6vev;`Z_?BGrhgMg%@=9&~#~cs7d1K8j;~auDaMyp8qHA-K!i{5xmq;HD zc1hJL6EnYhrG^{fx<}bj$nf$_(N*5HZ6p6=zuFRO1070SM#3MeY~9HJLm86ylM=MN zbz+KTps4C#CuXs(3>1DxAoy8I(AXqqF4LjV5-6lzT2LEQ$~Y}0__N#DhdDjDiND63 ze<4%`dYFtbL+FsuWKdqM#J8o!@(?C17$CKKbtKizx+@gb?)6L_8Qssdm_m*3lm2!i zyO|m{eU!e3y15+-@ZZd}NRvDX^t4`ZX7|H(9reXhf=@bj8RevKpOO}F^fy*YnXISv zVV|yUI+Oxe8G1X+Cg$~5y;0VsL$5woTSeJih*O@4@w4R7h4OWMd&`u{+pN`DUN*Ak zN7{7Ciq+(>myG8{8pcagr_(I8);23k3!0ia^A1#o?Uu->f_2cdl4>&vRWP*W^`JV- z;O6Ls-Q(xC$=%jq?Vnyhe24#_^kZb59aCdYKYF}T#?U5Iw0$#w(~f|cGVKu-Hh=wc z4&$u%v8TCOhgFrdn&1R^{_59NtbnSlVNt33FZ|msqTOkC=Ei!#4o(Rpg{|W5^>M&< zo~;7bFi*jb(PPse+8-R4Jayj*MEK4I78{VcJCKF>ZlHBwWauOG+W;1SOl)$8#MU7S#4 zp331UGCR__5h;^Bu3T9eC{p6wxpPTw9Tb+5Ll0|R3x$yJb2jZoJ_#Y_!x}8T?LpN8 zi6vQTL#(LjjeG?^k^hhza!924bUc@Fso^%9>k^c%Tbur*iw<$U4I6>Ex!y^+Z&RE3 z+B@ftw%2{Jh&%S!^WHh$S1-Qm8I#e*+io3jAj7{uKK97|=eJDn?U^oPu-fx$ZLeCj z=`1bFb7q~T)%$cgVP{No?)>hVe^ixKXzmgM9KNF$rYaEAZE zy`sdTQb$vDg5c!u=Wv9;G?D%oX0 r?&Co8bt0tjfNSpmc)S0il*_$UoA*Bd)ge>;_z%a(z*Ilu#6|AE*$-L` literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-aif.png b/system/images/media/thumb-aif.png new file mode 100644 index 0000000000000000000000000000000000000000..c63b446d620981dd1b535ea727eeff57f69225a5 GIT binary patch literal 1691 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 Y)h^B17HsR_YyV@}SlU?>oVZB*A6*SvQ~&?~ literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-app.png b/system/images/media/thumb-app.png new file mode 100644 index 0000000000000000000000000000000000000000..2d339250ab59551d4c496b622f3b840ad3c500c3 GIT binary patch literal 2360 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-avi.png b/system/images/media/thumb-avi.png new file mode 100644 index 0000000000000000000000000000000000000000..2a2490cd1af885adc7d651043189a448a58da5f9 GIT binary patch literal 2448 zcmbtWiBr>87o`-mm9|t7v`QiUtWv8&TSP?`BS?kHVi8#d1Q&`3$RayZvESH8AA%SYJktAtt~sC-^jO=FBrpMRF!ON(-Hl~ zWwZt_n(tp`Osy_TlnSDFVS8u$I<8))#uQW ziRA96iS{IN&#X)|BACGlIh8Zmo<%A~IExj|+M+6Xiq++f_0|OvY>6h^ltoUo3pS(= zNnOl!!A-*ZHZGYX6G6G;cXNbi^nn?vFqb}nUg6v&wxU;NGO6$y&Y0g=WB&A@7p|VN zx^!>6t$BWezp=il-WnHk%h{uHwK53boIvjGUZnob9Pu4%Xq+Ek*_1ku);y*4Pl&nM zwEj>+3s){#S8c?TI^D5#MU0`8sUDngjw@fiLu|c*sddHFE-Kdi#~S|_|L}H}(7QyR zl<+*U?;+_>48w&Elum_*>hWG8QTj-xPpvN}KBK z;!)=1;<{Nv!SvvmXpXwNEZCIBOm>toM@m_!e`YZ7rRg|QX9;uoBcJ&f6>*!`Qp^}$ zRVm)h5$9y$#<}qk0eeWm9$n!?PqtTc#u)2@1-T@I@E*QIf5XA0Q+gj!davQ$X?_1E zO-pMR&*joZuOxiQpmr~6-Gd$hpgs{)_D z)i$NBL5y>AvO1O0ARyzLvyU7WBHq99+hu8ri$EG*!40CnSvpGDv4zTIu$bpW8tnV< zVCAn)lN9LfkcGVF&^6s`UDD`oGI*x(hrVN_w)U)_=2nF`nGT6i8*akuOqDkIl$4Jh zzKgJJ)Rci$u%Gqn5U?5{!#DQMaGnpEbcd`<8lJOC2I&`YAI61Zp-+z&UayMQAz@G5 z$*Pz3#g!CGBf?k}UPJ?DQ=jX)u_-}k+s#XFT(eooZYuQE=5ur5P1;JXZ&32-(zNOX zTt0MlDGQvk8RN&#PUac9VC7bp_9av4PM~zw)o(F13V3cb4ZjKZ?GEn)rzBOMFN|eS z5sDh$_S(BR4!V8P0c6JtHgHb%Sutn|54%o;S-oC1%9Q=2S?dEMt2oc`=Z=)Ju37V1 zpv!sRX!Gah+Np-g4@r}hKuBJ`5l|4Sz`sfbr=X$K4nxZsWVgb)AM7uJSV0{l94yQt z$8g!lP^IRLuht=qBc))5#yWmklsJ^J{o{uV`vdygLva)+S0A8|bU*Y76)C^_2Yq8* z)r)OL@s#;H?|oyd!720#mY&$;v$C%7Fq=HLd^^nz@3ju!wwsOM6kpRrvZxe<^8Shw zc>9ZWyzlCk1Bm@)WI4SmF!lL-{sa^*4J@6qGip?t$@BnTXKfrM%6s(q;O;R9cYjQ{ z)a95lREKmob$Ohq&gp22tcp$lB@OhI#6pL}-aT|YZEzx$xtLw*hWk)c?D3SEe|o0j z{$6K&(^lBernwxv-AhsY3|J`8A6m|ec|a-x(E2_+lVcGt!EE^Nho{^+Xq^A#!vSr+ z_}N6I)q*K9(5SmL9mEvf6bjJKZe?5KJki&y%g(B{;TUuJ&Ovm~aKj+s(3Wx6gB7f# zk$uj!ktIzR1kJVUHqOb`0FI~t#FBfIXrng>*AKcEtZ=kr-*>p}Rq?3@|!LOkf|_QZcZZ+*{;ALjYYX zL*LmvKeD1q)K@KjQVn1DSOy4yc~X6Lx7im*tAcqJ#1fR(ryd2-10B4aqeZ(sMj>Lz zIfW;}F!==2+Rs(~dW~>kl#p>!hZHCzjD-mM=sldV9B|)Oz*xETNXRDHk)PE5>fT@~C&=#W==*0Z4mc;qJR{@H+^--^Y!U+! zCK2YrpRD_{;v2qp4jW#<1$Bp68WZ&IVN;7MwfRv&1Gc!#`|OLr48;T0F{l8bc7Aut zE>Gku&EbkldxJ-3qhL)y>$tnr1UI~)b7GwPcSVTdfbynhz{-uM(-n<6>(1xh zRQaK+YzI(fr1eVx43nJvm6aCSR zWBH*?RKSTpBBm7IIcNw;iaiff-9S=Z(lqJLDT%h(p_XkJx9&=c2~?+;IjxF2CfEOZ zcaVz(eb literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-bak.png b/system/images/media/thumb-bak.png new file mode 100644 index 0000000000000000000000000000000000000000..86ea01c24fafe652e246da8782ed0a1339da3dba GIT binary patch literal 2814 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 SPHVvehr39$G%7N@8Twx}!q#N~ literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-bat.png b/system/images/media/thumb-bat.png new file mode 100644 index 0000000000000000000000000000000000000000..1722dd037e7e71d714e98664a8276af91e2fb7cb GIT binary patch literal 2182 zcmbtU`#%#38`rXkBfaVJBHAgJD0NCAPOPhyNDIAMDIt>Sb|OV<)tlqKRIbGd4BqSo(N|rdkr-mH6&ic??kL@w(Ucd+({l?v#aMax{|e9Gk*kk7*u zADV9iLv9fi3upapB7l;`! zTn7|S7$^fe0R;6x^#j^!_8S;&fzj{K_6#`LFjxW9Yrsr|MLG0lK}R%DePMzFozH=p4BQ;( zdI2&iwA_JlHZUFmg3R|H#&_|kjuRuDQ6^j zbCGIX9hcy6ldKoakFAes#rds3tTvpEKY4<<9*+1Nxai;Uj5kKqtjULZQV&(-;7 zM2%OaVQArug>KxUonPC#8~JF#g;N5xlD5Z>%|nQsP65>&e?|gNfFY7OA#(CD53ORN zhr1u_2eF_A{ZY-}pc{LZbN}>+O7U6LD4Z_m6CIi{qXR&n@&FO6>DHpX`-9BDS;m zD<@%bLQUX9bkU+kWpotRTw#O_wEAzQJYQrzYIuR*2&Cm6&X0tUKyRwa4}krMA-t!`}V6biCJ)WXkyU$%yf7 zHGCY?RHH@LE+%nnFA_U^;$Ui%JD!_pvn6!vMxhqw9r633b4sAAu&UMftXGG|L~se( zbd5nTd+_@8JCbz?;)Si{7uRDb#L}uHD?MwDUQcawChQ{7S0?L32VL^3TsskP-YB{F zBwZ`~FrVCkFxiu6Q4=(q@w}YzOIn-*_eG1mmoKCsOw22-eCy{X^X8Jqe#s|b&dVC> z#YpU4(s?}PpnC`R>nbYWD@`+t40#%zJV{CRKvd#ThATr14!nJ^+fpKwMoeWQOg7L8?Oz}@XJlq!92_c9 z8EZt6ZZV>kJnRVV^R9VZXL_2DP~wC^aaC@QT%OnTsJpg==G2-(^Im!M7ZZ~HN^M+e zp2#rr$jn*Vms^TA@@ngZyech>3Nhd$&#ZWKx{+#+d%Z%x?)Qf`YEI>oxqt!2M z!YI(zu61e=3F*;>|C>hJBF@|rLpCOyt0_tK%23xr>Zhx zVjLM|Z1-Z`zSIpEIr^$j-y18|Qu*1ni5Gog1BQ)uOS=|ofIl4&8c@aM#rrVTHPy0Q zP%rqsk@VGkye#Mv_NOaxTAxNME@{#l)!G!fkx}+?XTQ#R%njm#Kl4FUN})V{+r8o^ zTzvCn`nG@3nD(BxbO}1F5~Gnb0%%C9lU@!cIDNEqx*8beXygA6p7Z}#L#d0JYJ;04 UhuGRL|8`Csb2?gZ`10ew0Qo?wbpQYW literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-bin.png b/system/images/media/thumb-bin.png new file mode 100644 index 0000000000000000000000000000000000000000..b23feb0205107d309a8c87efb055e5cc9954c952 GIT binary patch literal 1841 zcmbtU`BPJe7LH>8!`_If86R`0lmdcPtSgT)mx8${L>uv`$Rbn(N)ZJSkjlmN_sx92ob$~|Kg#18>2vfM z42F>hVqFyU*A)_leiN{=j3QO#(Vnd5mKm%GgLH_sxEgL2#Rn+t<-SUCV?!iZwD2beu zPLmIZh@cnfpZ$2(JT;)CR&cs}nE00%=~A{0vqIY}oMg=+ek*XRe zWV2-VJXJA5B(z~^omkI2RrHQ{I!>IZN8~EHYmOSJ&U>QGgxtdo^o|=+j)X*-aT!-9 z{=LbZ9W)YAxn_8EEFscz)!+5%(X}FEEMc+!jtfU+dFb47Zj#e_TmJp&VWsS5S$y!L zs=cw6+Bp3kD0b{Cl1PNnX*rWnV)OT1ZL%5PX~BV&j`UPx`iSuQw$wiL`_zyVorC;c z9S`GBbVf0VDu!c{Z+CS`FA5Ch73F$7E4?s{E9?uN-{&lk3W6t{K}X%1$DUs4V1|Q0 zVaWe>k!PrC+X6=E+Gfm#ff>gI3M2m0LY=T=9xMj0m|N@E%4B9%;c!rt4;)ks#0$$7 zc?PN`C&5`G{&f!ne&RMMBrL0;^1e%4ZbQB{e*7n5t!w)Sb8{ekI+C*^y8oEplzQYA zM--e792bsMmXNXK&by)I-vJib_y8+Z-^=BQuw5WqaJ#?MW>2B6?Ve})S*BL@>1j83 z#(eQM=n&ktTq&Ogi|jX8SxRN0y@MsQV1~ce;hAyJ%W$*6>@Q}C#aaJEu8b`TDFuEL z-b+6?n)?;B6NKBrNj;l8rMmAn)%(;OhBLtJZo;Dfl!DvKGyTtpZV>TdKR>mF(H>Jp zHkv04$^woHeB*c1I$`A&qL}YVS^F0t$M^|(R{ZdA`|v>yi(TP+-B@f5QNU|!H*3Vg z!R3TFBm!cwlhe7e`ux4pO4%cWFmOB4l;p(pSl6wNj)e@_$=B0d>>xET-EXwpT?jTx z;!F?_w9~^%c5&g2MQ*Le23Z$O_9x^!?T7Z61}xP8ti9?7!Tc!rF1J9qlF7<471@}_ zTIsS9ataCpSXpKww07%G=;Zo_@xHGiK0KE>uF-c)NVya@YzzLxj1Nv+xRL!KKVyMs z$o}~2qq~;UU-N<;Hie$X^jk?h(H4O#@mc)_WHU#1EZ+VXpW(P{Y&`v(2)qj06Y1?u+`d_kk`=5Aqz*+|cX znH-6Nm(E?9!$U!ie$<1fN%cD0~3cqJLajnDzSgK zYY{M&V$R(B9W*WZZEyL2>Sxfr+)Q*Cjy!)YB5!~oY0K@l{LgLK|965dd(qrESANxH?V%v^K@rhl+Zfcpt%2}e}; zjlzU&QEdYi0?MA^)Re6dM&vO!pSs!n#&D;5Hehg?8MXsjY0U{S*{`Mf9ean&syBg#7W3X_{GF%r*?Uz}>cWtJ V#YDC~J8}E7;Ni}5t8hId_&>uUyLtcs literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-bmp.png b/system/images/media/thumb-bmp.png new file mode 100644 index 0000000000000000000000000000000000000000..558496ed6a48908c387bceea05a910e4e37c2c12 GIT binary patch literal 2573 zcmcIm`8V5%7foA4?HyD79+XdwnxUAfnOdrtj|M5#)>5jqOjT=WF=f;?+G(PstB(_LMf-&cO|Al$yyz}n8=brb|J?Fjq=gxR&s_Cmi zAP~({aF{OyvJ3rhtM1uRFqiRLJ3}cF^adf2`bX-D%gQ@^SAwsHE2M^OD1bnApFVfm z-_4=3d1z7ig3VBDZT-@VBMawGcXw=WZ>J5CkTl8(Yzumn_GV&?xxDn8#TXO`Yo{l+ zwzoI8wvKl;uPYQ=+gtLjO*dTInq2;KS8EH8>x^wFWsTC8L;|T~Vs*s>*Dl}O^y+S3 z+*o(VwVlCtG;k*8q>^|_@95GZZBf|2Ft;L;iRCi<+^lzZ2Ww?1mP{0FtTUDuQ>guQ z(-X_G4cX?V3$|rrQ&Bm|`i|BSU`MZ`t`$;yet8HOj>e|)H5wAXF(yOP& zul3`J^Mc@>u9s}a`ziJ*Tzl3qwP}XKldQd+U|k?$2Sp13#Lj3E!Lz&lCZ$KTzMeTe zkTlTukvo-2?SIA^J=xVNQOKnVMdQqL_Q+sJPgfI<^EYR*e0+?(vfRSsg!bYBiCCYJhrk_JT`(FruyK&Op8~?mY4XF zwE@w>D>n1P)cE-7a@7?3k3mulnfRGIeRqg*pH96uJn+}Z(CtBT9)niE819`H%&du< zc^vMVcv8I5yC9G%%vV7}<}{XAIYKCZCRt_!BzQ z2T51@x;tlig-m)opWDHoY2$NmkcbJC-X~1@?*qNViz13}{?X_VVQv;dqhM!wUuJnz zJ`ULs2sG>z4D?TC3R%*$uiXs)T&`8J(ck?vSk2t@&^;YMNmW&4x6g4D9O_g2ld`#m z+qqpn?`+?P=#Izk};EruX zm5d&2nts477|*^~am2pNVk3v`wAS7ntPx+}c`Rk4ky_wYG4Vgl{jJ>V?2^`re4lv@ z)f^(GF;bJz9u&q8L3b47v{T@+y<%mL~Y`k7QZ{%yt zP|*9ch)TJWrtilA3)IE^n4uB*cc@f61;Dq!Z=f$9l?Mk527<-v@m9o9>`QOQ$k1WK z4WB7VNODs_%HQ0^4}#4odX@1T$h^32?2I=GYw0Q{?hmK~?B@h!Y9JSN#M zDLSjLK|5U0oi6Y|>&nyqRi;)CcenR-A%dOVt)hIEOe zL9gfkuo|Ltw0%A7*(zvMMbmDyHO%7z_#2|v^;i1l4l2DHaB(w0MZGu| zUHX`QI104Bu5PAG(9?f(M*_x5tcm$hw(1 z(sf(eyxsp>0a)&P$;NqU-!XgL3%<|O(P_1I?xYn9xUjp@I|I2eP`I!iS1DQFHVIM* zlKRY!V+zyub6KsstAL_!IuG}48!*Gq?2*{OZ*S*5pl))AvUn}A_*G68R-Q&VN4j1_4Awg_HP?6&;%NWg&8}?RKL_a20 zJy5Rz+JQURW3T1g09UBSyM;`J@4pjlZ9~+HQCrA7^6>dyYle9&lBK9l{u)*;_~0B% z`TQ7=U42#^NG{Yj2#%*lp)>u?PY3E09W|2#BW3|+lGd?H<>6H zo%3%g7tyxDp)YYJ9V&N1g%2A!OU4(mkUkJ z6OkSI{BzW2p5gFi@)N1U{MfzUebGUE$@l()*}Bjl&XRnM}U?*j~| zAiHG{Fhgy{jb-`5Q2&8j- zSTuUn>&*j~!zd)c_wN@xtOw2BhIO1lheD6m?oK)61nu9}XuRjD&%wibAeUDO?Ae=l6MYlvA(IqcPtEq@Pm_H^$MO>(xwZ?*%$}>qx<311qGVYISp@ zGFS>VPlsC~Ej2d%s-PJF8JVp!1`Nj5IF{_-->)qoOKs=}{oHg2-~%KFgPB^7pDP{Rj$FBku-EhrZi1!i!5m)&dJ}bUm(kO^jNg*?lz?*|s~~av5RGcS zzc-}1bItOQC|fIs0^kh7(#S@@zIU?(zPG_y^i>oB#<=bMfZ_6?=(FQZ-5KV3ACI*E vt^ualu4b4%Y~A?IMuhqgPt^Zc@BCX3)6568_O9IMf2C7yXJ9q1zaahrHCG=U literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-cab.png b/system/images/media/thumb-cab.png new file mode 100644 index 0000000000000000000000000000000000000000..39d450fa59a84209a9f6ceba3c0e182113dc69f2 GIT binary patch literal 3056 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 IyH8&I56j$9wEzGB literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-cad.png b/system/images/media/thumb-cad.png new file mode 100644 index 0000000000000000000000000000000000000000..b0a59863a8d499891d6069209387aee5c7494206 GIT binary patch literal 3021 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-cdr.png b/system/images/media/thumb-cdr.png new file mode 100644 index 0000000000000000000000000000000000000000..1c106f1b20e156c3301b1d1e5e140daed5411448 GIT binary patch literal 2713 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-cfg.png b/system/images/media/thumb-cfg.png new file mode 100644 index 0000000000000000000000000000000000000000..187e892aa1471406a5ab07b74d7801e324fb59e2 GIT binary patch literal 2764 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 TvHYIB{VxLc^z}fVxeogu4(QAu literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-cpp.png b/system/images/media/thumb-cpp.png new file mode 100644 index 0000000000000000000000000000000000000000..059668adc7c5b6e6a0c43060a9348413e5fb71b2 GIT binary patch literal 2425 zcmcIl`8U*y8=h{Ykan#!b(3mJMcPculro~Uh?Fc@iXn|mWJ%3PL$_;^rA2g0n@ADE zj4?9AjIHdAbugB(&lvOB=j)#D-*De^-t(U4InQ~1d7pEhnA4}M=4-Fg#^G@Dt;v>W zakyEme|PTe8HVlt1DiP-S4k&HINaAb9mHL82G6>A*6IlEbM2ZT9PW=@r)}+zSrw>a zYr)15^rIMNRDt)a!8;;QtyT{zv0gdWHHmtZ01&E1F*T-EfA2%c1u6_vgNWLR2b|)l zcs+<{5Sauq7*!W^!*(2%_jf=bMF2!SJdLHdLgi9Ks=(@G2)`FWQ1t|WO~dNW3G`wy zAP2EN1;&xU!U4p(Q02-8ih2-7p6XTwnAHy36sjVG;5n}9StB&6!cv=|kV-JQ0s7Vj zdwvJ{r?7!348hc)mEe^UU`&Ob<*I&H!SItnv2+FJh zf6CC-Q8cLm@(}=WKcNp|II$j*OrrEE(5(ch=tn-a!S|}b`eC$k0Abef3UIhNz1Ef_ z`w;PH`+#dB8FyDQYr&f}UZl{(jRu?P>tnUGx3q?h^HP-~yq6uhF5^6vyw2eL+Mtau z{G$D)Yb6oIrVM@!H{R2fdt@e=|KV)UOPW4uWtW`jpieU~**4e_%uYQjU+Wujn%xlp zC?+6Pw*RV(@L9i>FTn!xt)3s+vdfZ^;v8fo)c)jsgsu3t$YF!9=}E45RZ4%{)DFI) zHKy?BHmlCbpkxozcem=h8N@`>oZx9ZgLp2U>$gccxOS~1WT}1&tSuk6I6(Uxho7xb z*{<5MPdpH^*N~kL=w5NM-jfE*HQm9_r;?`z$&*2Q1IOU`rV|ea;?wX+<~`<$o7JmF z!5c*q3uYpEKO5g1OMR!h@WrY3MkAzm#VDeez)Z{)ju^h_zh>~a&N@CR^CehJzi>bI zMhdG_b||WJ zM<2UK2A|r@G9hs7+LqhbJ8(2tUg!E=*ma=hmdo`Z-S}6v0mNU)nYDJ=re@@273CwMhN&I5p+VJPtkg|d48L_GfP9d-66`DN{xxH zfJmHlcbjjFHGy+|WA(hO!+!EBDXhQ;H}AIKwbI=wk8}cU4(hcr^m5vVwg^2~e~MP6 z`y9BK$Q@54X%%iTN>z_2#YjlX%(hqjp*x#k}^13D^{;FD;fsE*E8{|#zz?*N;+{%j_#rN z4?W~M;btZHZB$ymarq~6dUYGb9sMeG>7|Do^GmYsY53Fxh|!x)PdgTGKj<1buRipn zk4L0;Uc{ZZw+T+>a)R@UJbY^S;F_{0TK$?Ina%!{ZuEpGM04fNx+1*DxaAJhic~oI>Q|mlYRC%vs%uIDbk6#@El@7%PTQ&bn#h^?VRM;b&vx+G_1McSb}* zksjUOa%I_uw@xp!S9)HmtTdEuWVNr*EL5aUEc2|kbpP-+Nie$z&qxp6rG1DLA87SV zeAG6<)t6Q_&)7BMi~vthPri@Jado74Z`&pPvNJCJj80nM>+39Gn(^M0zj&HMh#Mt! zhd6{CHk%vn<(Tim5`{Po{q;A@<_Cdg{KcLR{4vPMp9+?Sh`bzJqa=<7>`P%Cece0B z^cAlYo{;5%DhW+K(V-EO?-M$_NVJ6SqY=Xq$nSj^ytTUMSfRnf8TdjJ`Pi;~4k0ah zvk@5=nVV33nob$a`vd2HfNwV78V5K7nRjg1fvar}Q?THZvGZ#1D>*2(?gIrQp{ zK9wVxOW7ySYs7rc^>NpCpm>{K);oYsVm&p&>1=tS4XZmd_`+yved2|4XAGyUWO*w{ zuRp#$8(e2~W*Ldz_#~^Px91Ltby!OAKTFODurMYk425s=5y#Q-_o@g=MBST@C+jv| z4Q&s)b9v#0-O=L{IS&*}JUKI}cs!Q%XV_zbJzc}&`}C3IHCn2DW3lVWZpoMYDF)9I zZ2hRI$5}Y9vbPy~Vt>;d!<+|FVxC+ZFE%i#%YID{-0d_?QAaD^@G+_6BfT?Cd{D~# g4;$nE)<7hvHD~FGPhlf8^}meuu~U|xkGRqQ2c3rsEdT%j literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-crx.png b/system/images/media/thumb-crx.png new file mode 100644 index 0000000000000000000000000000000000000000..d90a0e7300d5fd3904ad7ea624c4644b3abfd6ac GIT binary patch literal 2965 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-csv.png b/system/images/media/thumb-csv.png new file mode 100644 index 0000000000000000000000000000000000000000..730edc7b1781c6a02e84276c3d5538dcd876c840 GIT binary patch literal 3317 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-cue.png b/system/images/media/thumb-cue.png new file mode 100644 index 0000000000000000000000000000000000000000..98cdf358422e7859e671068a5432a8d271d57d89 GIT binary patch literal 2271 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-dat.png b/system/images/media/thumb-dat.png new file mode 100644 index 0000000000000000000000000000000000000000..dc4c4a3cafa9a3c5a4b77a45fdd7265ad89b2c9a GIT binary patch literal 2188 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 O=y2$Ks4k!n~x!F`LgrT;h~7SVGo&2 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 I-s}nV|K>Tf6951J literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-dll.png b/system/images/media/thumb-dll.png new file mode 100644 index 0000000000000000000000000000000000000000..469cd5a6e6877835b83c2cfc3bd5424d644d738a GIT binary patch literal 1352 zcmbu7i%(N`6vyjmCsL}e7BR?Xr(N((HXSfy$Vy|gLCC+B=W$v40AJHOwhy_pnT zL`(zK=G*aZE;Q+Bs{M%^?dp_}cZ1((X{f!sce-Ztj-FZcs4mGRBIsZVF& zNpw#q=j!DUXFybJcFfwG@-cahFW}@!7#j0{OxGF+dMpl!#@s)ys2f-OF`>Mn(7)u% zE|1B(d3{h^Iv_eTt!fMey&WEXEs2)x@aVLbAvgrP zy(X{UK5yz(8~+W3VB^BH*)ANGl+PKuw3a~_4o)a<&Zz$~*{4;;?os&_wegL^qYVaq zZl76c=v3(2#o8xI{mXGht+XT#f)q3lo-*m6Xl;0Ycn>6Oqp5#TlO0DFV6|38> z;Hus6`J9n@+BhY@3^vk~19U=ucAFPELf%YJ=Q81(c%Y6>=v!d=F)S*0H_2Z`tc#!$ z9{JI%EN~7!x2OtD?=?}$6?a}>sVrjJf#yX1zX6mYhjUWSh$J=ug2tw(^2;x+Cg zf6r*bd5uRiKq0a6%kI`L0x5yZrh+B(-gj9}bx$Z9zn0W6%#4n}#~%G9IZ#e~L*B3H zIe=cayk*C_(hq^3K}awXM@KJ(Mwv_uLP_gn8vzcz5Q~phrzQvfL=0VgW*MH8#P!lV z*B1SWyBqPyt$-fhb|4iz1Wg;H$ZgVc z4;O25@uaQX-Un%wdC3?k2b68taO#7vS*PM4rC0I0tMe_V{awQ4ybLPvDzU z$o+bYr+i2M E5AMOl?*IS* literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-dmg.png b/system/images/media/thumb-dmg.png new file mode 100644 index 0000000000000000000000000000000000000000..ea35c4e4f88dd07d29e0fc29865ec6311965845d GIT binary patch literal 3064 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 H4`TiY$1Ong literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-dmp.png b/system/images/media/thumb-dmp.png new file mode 100644 index 0000000000000000000000000000000000000000..9887e631e82ce8f4454f4a0a7ce430b130574b36 GIT binary patch literal 2527 zcmcJQ_g9k#7sqLhKv{+tEQ+8J5VQ)23R=M(QC`4MT z3_&4`fD+jwf(R&K$dJ8PWXK3R&&rdRp7Z_-z2|(-J@<3&_xsa5=f+;LGMAT8m64E; zkf&0}wh|KGF#nPCwk=Zb`Vrr11P{OxkdP=$ki}fLZ_VEX*qWc0C}>lglaTn<;1bQ= zO!({~HrgVZ9TLrtBeh9r%PX|KP%IY1l@H<4D6H?Dh~I-WvXJ`cXiL6Wgo$yH7!eAe z(S#|sIJ_Z7ATbIRiZ zlQ8YFXl4NYTn=Tr;%f`yEyK#3XmJV`Op7rDseLB;(u<9^!G&RBcvJZNXDG)@m}(D~ zMI+UJLfM{3&0jD#L5xCT4251?!}@APlbr~ciMGBLCRyPd0=VJ=C5!`1UB>$%J&z|kHLj>l$Qd}A5y<_c46;Nm}U zXdNz(MLUY2+*_iF4t(hglOjZ+m^a2l4o({L{n3Sw^AKv)(K2UXHGw<#Z z%H_l&!xfOH8+~?PYq=`9c`D$VtcJVuz9<6Ny-{B@g%6Ov11%}&JqdH20jcBM}M zRW*E{keY2LO@1MU9P^CR)Rk1+QzDqid$>cRf$Z{lzfFXibpLRO*KtO!LkxZ59MRz5 zWs^Jk8j?xsegQYLM@T+VU(c30DiBu2tdjQ{Fs{vx_3Lth^{a<_^azTM$jIX|22cPS zITSb-&`c=4_l7NWz$0DlrM=OTUbgWoPb}zm`PKUEEfyw>P3_epC7|KwdL21KN&-Dv zsYvhaTh3A;k<1-1Y{UMY+!?Pb;Z z?GXfg(X=dlMB?O54XS;oF&#Y`(hYf$-T8l1JE3zDpikrHwFO9w!3@Sut;DuS zV<0lnj@7Tsr@MDHh%Xc@>x~S^1Kv@lfMhgXLqWR8rn2eObW?3&{pexHZc*w-?Q1f3 zuBc!4kkLRwR18ga8;^4R9>3YkZ(-|gr{?;;45{12y4;aEu>YcAJty!CgmTQFVCPOl|B?o6BdCLCaEq(cFJ5@Ee z-kpo5sX0Qm*II#0EG+LH=lWZej@CUy=sNx&&9+fbu?b|vH8=jP@I|}HNYR*L{TQAk zgizH{yH%2I^P~wblltibQ#ry@r~1yhRZ_F#li?oaQ&K6ULz25Qw|m7B`6C@clO8#q z1gb9YrER_DtSfLkT9QqogaW7k_B21CV?m^U?6ylF0H0-S9XaV(A6e$uad>T>K;6$f zC6XtnV9Kg2LoJ>K$Wmf$=#DAUcj+>I8d`~+0!eHscIAAy{dM|u7g0;+D31oRR9XDG zM}_z{r+!sKimCG@rXeBrdpAlrCpFI}Q%4Gy72A;IGs0_xXXj0cc1*^FxLLq^Td#{H zF&|hx*&uCu{6l%z+%d3UF0oJV7lNYi#kGa2AZxdqMXhN;Hfga&cR`Esfn)~Q`Op9( zm9Ky11S&N+GYeJ7t)$8LvRE^a)C=C#+gTXV+F8~aM=AsPzVdLw{)d1?_gK>KHFKgd z5Y6$F9(riNIQ>#MM-B#ADq7`ZP0OF2MX1?2tCN}Q5wyLhy}4e;r%B%l1nkx^8uKp5 zZ>uS}?G#wVww@+UUT%*IA^>t$ZYf9f;~}hNw|l7r(xRTGLf@gQ@6hXj?|A0D!6~Nx fi*M=w(YOGYJn*03wm#g6{|};?S&<9Q-vIvya+(&6 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-doc.png b/system/images/media/thumb-doc.png new file mode 100644 index 0000000000000000000000000000000000000000..7cb1b757960c5a9b70cc9b4b71d4f9f5cc8f522b GIT binary patch literal 3051 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-dwg.png b/system/images/media/thumb-dwg.png new file mode 100644 index 0000000000000000000000000000000000000000..3de942b13ca7e29c4ca2fb2c82ac9abaf6101a6d GIT binary patch literal 3530 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-elf.png b/system/images/media/thumb-elf.png new file mode 100644 index 0000000000000000000000000000000000000000..d935be350ccdc7b0a613670d6fd4da61bdac727f GIT binary patch literal 663 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9LzwG?TN?!0V$SrM_)$E)e-c@Ne8%D+ zcPEB*=VV?2Iqd;HA+FwQj#cfuwB+Lbw_ktEJ$JYB=(R-`?*0G&zvtNX*27mHefaX> z`>&@Tzn*>i_V?d^H(!7J^7HrWFWb=~T+rGwZKCuHAom#+lotdoI5J_Ven?4-ejdX*zf%b=#SL|NqZEd#CQe z3=9dqU~ZAPZWEXbD>je(ZB!u&-YHe(sD0O z$9KVVo$AleqHT7oaRR+T0XSf9E9YJvxAuLvzWmhleoFa$(ZWmYJItci@^|UCPd)!l zDL<`=L#aangZQNGaf@^IHTe{|T{m;HCv4tP8tZt8#gRqOMF5Eqfbb?rHQ$-bqQjbL z;&I_?SW^wJ_a5#`6%&HO(Tz0`fAZNRLD56HPH*4b=@mjtJ}T7daiQ7Y1ar-%^NxAD zME9)Wp2WI*ebW)vl~0OdCv4zGvY$l|=A@ff4hQbJ`Tmz#*{uIOpC3KnC_q85L9|*w Ym;QcYwfzPiH(ro{r>mdKI;Vst0J04F-T(jq literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-eot.png b/system/images/media/thumb-eot.png new file mode 100644 index 0000000000000000000000000000000000000000..a56281a15a9eb3556d8d5dc932721345c30302a1 GIT binary patch literal 2002 zcmbu9_g52U7su)QvT5XH6s%B*n(9F*`09(JKy3;ZkRbw!2#8c96%!Cd3YAo;?q)7w7X^?mqFAoH(L;)E z!}ZQC>vVeVx^!EuX`?U7RBDw*TQEd9*D~Hd!wYK}4@Y8Wg%Xi`TcOq{HCmx;J9~gU zz90y0!d&Yj;1&fMt?m~zb!dLIev(~+hne8l7%t zZDUy?|Dls`z7{am-&wXd%s*4TZ9FjLr~jHqjQI5Lq{SCp#HS0@*fc zSrpZZV+uD*#^_s0b!hYWym;$9m0O0HIomv5ft^Y1oy2h0Vn5)=xNCTxFur}_9hI9q zNFJD5{S8A$&-2w)oVg+Cm+Hb*T;CNa2u{L@benup}CqTUBdyl-2bN)YRbI>h*5o zg|{h7>zjN&Iro^zDjKFOY)C(@h~Lw=RQ@`0dZCNCbfFbDC)&a+tj&qH>WD1pFzsR+ z?hnE&XI(0iZC7F$nf)K(IK~7|*c{{7V`OAbX}R`MJBPS?VD9$UkKg^) z)|SO)H=nT2^aw}vJnBQCa@Fy2?(4&lYoz;(*hh+f_7;&>>oS>|1UUk-FwECKc;4h= zlUeKS)lQj(CzyvbV<`g^|7V#2FGB=zsPcC)u^TI^=zHJ#s^1>8bCHVc8ujDAe#=g$ zufn^8));3_Zc7g9?DaTcPHqP4cGr6LrvCzDJMfSIn^(R} zM{}|d_)^{C{bIHQ!U^i;W$)v&?FnAnjaY2lg(<#ahaiDVos#R)G_kNg(_T$rb4=Fv zupHCqi(3x6VoVz2VYJVxiA-vG$ME&sgv4{O9Fyn>u`rK)&%$w5+}|gdDW<1YM;{J_ zy#?-5`CZg=Oz-b-%lBnwyLVQ^{kH2&#d4Y=z7R$D#=Rlxc#ZnxC6~Lv{FJ$&%!V}o z2D9WJ2jLV9_szT1F8`_RN~KDhP!wDrAxVmK>uym<(&GxjYc>d1Z&Ju-4h7O1t?8tn zz!K|l=iScBs4DjykjJN`!3VIu#tcVi-d)N;Bzt;iUa7qa&I>!m$^Vg5m;Bz^Sk`3; z&xF+%`R2V^TdPGso|e3T@|+V>(;XsBjYr z0SH5p^LqmZ!0w3Rc!KkZozBbgCOA(lUYedO&t+{&N5Nppr(~PF-fs@(CY}}cz5vFV z-*)%$D#(>%t2o}_ureUA0+P+a@B`6+KW$K6wojU@;5MMyhYEejdZG z{n7x5iNrU10wrg2k}3_pll28lcL=&WuOThw246Nm>ZKTOA;w?+Dxe(v^($m5%P|(r z*l&QA*YUN^?Sq`IzK76AYw`^2no7&?t#p~PzEtz9_FzDHynELhh_zo?u$9=`)zVO` zozEaWsMMkzM+agFN3n3JaPvVW$7&$~%KMzx%-PcYAjdwHj_x*e1pcH^UYvXl5Og-! z7DNZyQAX%T%t9UZ7UFCXL13tLW%Si{)63;ra~lK7Nu`6)if5t-vY^l&G!Nu1y$%^$ zn^4=o^tf(8ZUCYi6B2IHXk#IKS~0{Do&l>ZE_#y`skM)G;^pqM0j&VJ>(TeB&g+Ib zHJj+K{Jo5EK3FOT)i}Ib8gvR!Kbqxf15yBSvJHW<`pkN&V7Th2aicHn+?S5QmoJ5- z#--pE^%wR-se1y#Rfwje4VhjyQZ440mP9+_67wT(A-_@^&~_1LO3UhdPIXque1cpC zD`nkQP%5W{>X3B2I{HYOpiDU-?*IBGvGn6UyxS_vE#N8ik;ZkU(CikzI=kqQPu8uR zy#f7DH$hXrU2XSMOR@}zn{n+-ec@_@Ctgc`%2#Yb#lXOE`pA1C z;K_gV85VGV7${pNU9i;SNQXk&$3f6Um9C8`Yv~*7`rU5i0PL8)<6YG6r=ntVY7W`% z#*};Pua85ir-w*mnRE405-Rr#n}2ZS{%-|F>HpO+;uN{XC^7q62s|C?*Ye#J=wCVu BH>v;t literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-eps.png b/system/images/media/thumb-eps.png new file mode 100644 index 0000000000000000000000000000000000000000..487533dc199149bdd0135c298cdb94e8fcc2f745 GIT binary patch literal 2276 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 M9Xalrd)S}#e~OV literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-fla.png b/system/images/media/thumb-fla.png new file mode 100644 index 0000000000000000000000000000000000000000..ece67ffa32cbd35de8463eaa022b443f417b0a5e GIT binary patch literal 1724 zcmbu8i&N7F631~Q$kQUecv$?6)mp9!77>9#Fo+sOKqGAg0p&<-MK};n0Rgc|uZRl% zj8qRLC@o$Kh!yH7F(i6 z(Vos@J)Mn`F}->D?(pFFyz!$-!JpHbtk!5UK7XKhNT-RT64i?fN62`BT6vM#Q_f=x z=5+V?T;;;N#L$YUOdRcO(>dW zV%F^RHw@BCv5>8qsTT=TnB-JukJWBZqLa?ix_^<3%8UkNAN9?+bb4vAdvfB(Ar4-p ztQz6nyY!5*;Z^&UYxZ2rvg^Z}4_<=X@t7PmxpUctACNs#tNkzB0Y{txF z(I88;xNw%%y=1i%a{7#B^Q}Qv8nfr^xb){yK6z60S}u7c7~*JWv-;_9dN)QmG`GB5 zClX}$GyWKtCepf3QHW7wJZVyOgVXnIA8k}WH>}fYO(uic+$0k(S*??c3zhuAnWd!! zT37AZ$Z-mxOFeO#O8hV}PMe-=l}jvko5ZMpJ|-B@%#7*h^4Lu3w7QJjk5efw_fj5h z6U?kyq3~2JG^uD*BT^%_AiTTf!{BRQVBMxIv9Ismq51#XMxppuoy!b)CMnTT=OrbH z_8~`?y`AgvGqRexxkzcppG)Z*y9(|IYlWxRtn?Q`_RGFnELT9$hKcgOl2Z5`d^|54 zcK*;Se);F=z7;|EySj!OCsJDo2oR3WhH3Q#;FVMHZX8{mhN|&yEYbfMU%#!90z}7) zlqt~D?gj-V-s!0`JOylhtZ6?Fa#5@o*-xubHCvUMt&cTa`vYJP1b4%bbJGnD2g@Gk z7Q{UTUO5(9VO3@S@I+EXalTDRKjqQZIh}Lx-)WFU))$Q46lm1)Y0wMRdCpR(XhGF@ zA`>-0i(^0(gA0*=BDfs3H8}_-hlc*^klnPu6o&xG=txh#clF*%qhmk>gZw@(@PdzP z7pXbX6`luvno+s==U(Q4JUEC#M{au(mfmFbcdGA517pzc60(uaqtsFV~{_Xd4G-``CvgCpcZOlwVRFM3kuI}v-`{qQGmpflt zngA*U*OqCuQl$^71Ml^veVbX$NWE*Ra6yhO4cX zC&NMd=K?q3A`a?3&PwkYZrf%M8 zEIm?O`gNG-{UxvtUFxHjjA6i{-?J)!@C~>?@n%mfO6jL0yc=kI=Zv`q;(1QtdtmiY zKJiH?1GT!yJ-FrBs12p`M#h8Z&U-+%t zqolALgvNtSMcDVjnt>{8S8l|0&DD!o53dgg-V=7gHbS-cGs_lk-u-Z(Ix1rij0O#* zk9p11-_fzPkAHwPZBmYe?EEf2vaK77Li#I{c05ZizfU=NShH#O4A5Vo_%!g^bnf8h z?L}EI@BzB6`ZJ4f_Tcji4VTua>a*v*nzsE!`%6IR6wZ5Oxc*;C&dAAXSIkwg{@sg? zuBbN3I%u#5rl>IMm?GZma9-<{-F<*Q^I-Fr{7^|_P}>RKN;Ko*pD5w>NPT~Dz>O83 pE+QD0Ag(;L5%Cv({QsjSoym>LkwptI)S)%y)bWJaH!+t{{{w8thZO(- literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-flv.png b/system/images/media/thumb-flv.png new file mode 100644 index 0000000000000000000000000000000000000000..1dafcd41b63bb5b0177e7bc81578f2ccb56b42f5 GIT binary patch literal 1720 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-gam.png b/system/images/media/thumb-gam.png new file mode 100644 index 0000000000000000000000000000000000000000..56ce110091d467f1a36262c8dbdcdacd38fa7438 GIT binary patch literal 3203 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 h5B=|o&;ON$t(f#G&@SlYq6zHw literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-gbr.png b/system/images/media/thumb-gbr.png new file mode 100644 index 0000000000000000000000000000000000000000..631deb51a1e6a61bdbd76f6032132b83c1f27637 GIT binary patch literal 2849 zcmcIl={FRL1D#$fOSULUX^NJY9@VQ=`>dTcJ=$i9{|OF8TUZA(!*o-bnCxUehaf=2L}C#z&#}NF=AINFkMS zU)Iv@-jVZoG!J(fgCQ4*qyz%p&sWA~%V;#ZzaI_a!3YUvrNHDup?H3t?&T@taF|h1 zv^%%uVlh4Nfn;rs?s=CU7${v|7rg5ej*LjYedBkdq$Hwf5+kKj*#!mS#YNG?1nX%A zr?P_XeUA|l&V)iaWu=V29?=8*g&#ghH!0#p93w1LJcs4hybyi<%n1IIm7FA5T~$aV zyp|Tp>UVxeyNpWZwYKn@o4K{Maz3B#>mwW<=DliU#>R3=OWB#3%;+dya}y&hjPWRh zk3uq|B84A63O{{fLLc+HyG0Y@%%ntiexBgnJ1L1oyA6?&$-?1}V%!2dC!2oHOU7hM zDHIut$x2IO{29bb|6BCsi)4A3Q(Vk=7|3mG;8s_O7Z$ixRrLD-qRC0&=%{36MbO*J zeepsxJuPFgSQ+VZE|-;-DnR#eN=i7-E4lEO{H`w1_&E1PHT}VTF%~OcTH?Ka%}Pn( zRFpF!BlumN0yJ7K5Qt}I1%17&)F-T@B;n8yr@WkSK`xO9KMV@qzvtH1@jE)0u`!&oGC@xd3zp0& zDrCmR@!Q&XuMw;#Da`mdRz`*hgW4cK%f_32UF2k`NX1VdkF zY4=Ls3eKLHCJ>kvpqHQ@R_Ii!wepYA|91d-?ACw^H~rDo`y8jjxi4;dMVa{JRQ|5k z^SFp^=hJD#@LX41`?w5EeEX1ZiTt=ZPnOrE3^vgvu}xk?>-({4MKe9FT6kG4D@ z#(X>M!q#AF+tp*s4hznH#&+bGt)R;9d|qRZDb-;k*f>4BE(2)L(q%dt&|1(+9VXKi z`B&cCyfVQT6Rzl_14HvjH&^AMFVd+6wldb2s;hN;69Nu2{*<1P-em`bc z;Dvnw8ahN=7^v^yx%iu#$7G+JEV1>h(}6<_&)b=eo+VnFy=fTXQ7ms23l?q@YS57| zWf6+^#54DPkz3zj6_6sC>b&A>Lfnd}f`AB`gy1;P!$LP`*}2Q?8Nx3HG>f=QDY?2v z+b?Z@WSyrLQQ?88J_qu2Ou!m45AaC0?Y_rUKY3|=e;6<*6bh#;S~D$^^Ung(tERDw+n$ZcZ} z%p+97+IqclwZ18Sk4+}&f+_WO*M!RRK`?}B4C0}&U96WV*L)AM_V|Nb^o;aMMt<3u zj)=T&V329yWtXn+L?ssB?2VtPE&CWx3?AP^0L#AIX_zPkMti<^K<Drd;*-W68Fxju}> zYN2VCmT@9ub8fd&NZ0H0dd3|&wJgv1CG}<_XxhYE+T0C)oK;&l&@|k`;-F>aX1}Sq z)M<_*SlA6N(~W~)OOcXO?I{Nwt*15O-H?#ogwozfMnsI(p#&#G->4y-51I(oGhjUG z$a5_))j7MUH)Vg1UD!BEtXiTMn)(N*Yiwz5?^Pf7M`IHkAkvLO+u|o>cm9NiUt3lo z#bm-JoH1{AwLI!Ej?)UwvJCS=;qcF5%j8J!Bgl$tazX2_T40YYiMA-9)pf_Y{JSBn zzwHKtO@7rPI8AD{B)RRp>L$;0f#?#V{Fg{#O*6A|T7CtV?Kd`nn}wSXz1(#P&LfY` z#Z|uEp4~oVGZKXJp0d{Xv?@4Lf$x|#n~j-|=7#BjNGELVzo%F}k14(sc)+zaQlj2+ z-CUah_0K-_SnITV)!8nx4KD$A2-6XACe-J+^Zr(1W^&5O;vJB4HIa33*bzy^iSlQa zubMYA7+;jbqL~$te2jn+>#9gGG;$;CRS9cg12qGc^!HG^MhS0E(XDuEEo+`VxY0Xv z1r(@a=`3tkxI!zXHm%;LMrEZRjKYo|J*w9c(9m2G+I$vv+-(lubs}AOQjR_`CrGf_ z=Nu8#l!3!Hy)qlVJRDG?1HPi`5#XlPHXoelfD2~GIJXieEu}X>8)U)oyQgbGIV6XO z*k|7%AWB`b(JyNGr^kYDUou~HZiZj_D=%fu((CnZ!k1GVHTw(6&P_=3mpd}lcSAr9 zsi^1+`kx&vY(cIneZT@Jy%ah4X~3(ev^e7p;d?p7zmi~t5Z!W6 z&$v-E#NBWD+iwh!WGK)VWYavp4BO#kYOu5OR645W%D|P!w-)vCU_*OGR_wJb?fTDV z-oPp>B%4Lm501$@tM2vW0biLWE&_jStXluGZdCd7H_7^mDv9KI4eqzx#XZP%O>Mu7 z-h32x=I|<+Sd}<+KoE={!=W4so;n86B)Vo1Q(M<{Joc%$;S7cWwSb3&D zfj~2{>^J%aoO?oK?eO!iJW_e zz(rpd=tym?AsOc8Er_0LeX$#DHTas zXCmti!`SzkX)wkNW-w+9W_dsN`xowW&U@bTJm);`FYj}n#49!y$_o1x5D0`a%F^sA z0xmialgE_@>aS78ej@Tjc7gdG zpuUH-C1AG$oDI7 zZC=oP17=#F#SL11hZa}pc?c2_%yt5g4qee8S%bAj;4^?T0E6iu7DCSh=!yZ65L(@# z^$%F(!dw>!=YfF-AqS>vLBN9X0vJeziAv}Q29W@IpTKAyEKy;78N{nFSp|%5z-)xR zBoM7YM+gkR1X39EKZTAU7|RFRN0{w^vA>|*9|kZ$c?+C=AcX?K7l?OYVFXs#K)w%a zJm`H4q;Oc}KxY*6K7uxHXuSzD&9E>G3>>t2067lCYcO38ECNhc!#WRslmYQBvEg0VNi>4U{_;10sVD9|f_QVffuFp>>R6j+{u5iIm2!blEK-@$YP zbp8p0&!I0FCcZ+~Jy>DEWDN{uz{qRpe+I+ZK#G7LpFlVV-SIH-1y&b;Rti%!z-ohm zG+3U1>F;{_PKpS`)?kzw(*Dme4n^p`$7l2R3FM0u*`0P5cJEM3+J%%kIfA>WYv&ng z*B_b{Qj$e06Vjq}VTwJ?4Ws#g)M%Qo7oBb3;_gS#bh({)ePh(-jnw@ERQk=eJTskV z!wM;QD67i4xE^ZcrSW@K8#tn@lg zxPCoYhSsd@ZXGM|EF5`7Cz3c1-JK;D_R9EjQVfVo7j*V-&ov1bRgy~|=P*@A8u2$| zks%-QQYr}1vNn*JTNl}~kk<`1b2{pbG^1(k$^BkkR3ss{u4=wg$9yH&eg0WFAwN7S z`iV)DqE^-rQ(cs~z2aSk%1#+yd3V#L#bVvPCwLk131>9|yr#_J?Wi7ULo>JZiC9~` zPv_HBri@oic5yZLtL!;wc(81X>1h%&oiS*#X8h%cnVOfuNRxwNl2Q!2ng1PY@ zb&R)tT{Yt3(A1@JIbUbjFxHiFA2&S_GJP#d`)u+NMV?i8tlQBxo1({Oymx3B9XzxU zyUQsxcd$`yD6~iwf2lI3NVA=`*g&+flYJ#$74UI@KA~Ll89KFy+B4`>{E_#XfyIUa z)Qt23T%R_b>MVnbkU=sp?$!EFW-N}+N>zPJA!=C)$^4OIJ00yR^2lM*M_E*vESR4% z+r!XcTaSlVs-ou=(N8V*FqRaU`FGG^XNu)CQX|zW-W@}0r54J?>AcZw#2!n~SMzf6 z+!xmXnx5>&1wwy;@pYxqUqg`{_ZFoIF{U+Edu=g33C2Fp$K-jAK^9leA|IjW9-Q9A zXr<@X?X+b0f^x&yVd&|NUANIOAH)Xs z-k6(->ps0QMwzs7smnIr^?UsF+vtPJ0o0OS%}*A)z4U5GC5~vBxo^7WyS-wAqFqX5 zP@n9oTSB#a^65@``adx!O~2&UfTnt*L*<_~N(nlC?3cBgdDD}V@w_;T$`dtR>==KU z(ch&xRgV6rLF4jFIt(iY?P5;%L>Oc688g z-Q1sl;oW9lK7KrV`CQY@Fl72LFW1~*mA@ESx=(JObgsXa6}KEUzJ`Bm@KtgGHf?jl zRk*pjE-f+0n{AB~c}BNz%Z6dr*Vg+i39r)>oT3k87km759TR!AIg?wzB2bpi$~o>i zpZ0MzAl}+-5c>0!;?~#C_fsVLaodE?idxZD{n@!cCurSBHVFuB&df+Pp$bP`ujR#S z#s(fub9s$39o_1ixU%V1R72tWL{0xGR7<@P=kX~n-a#Gd((v4j!!ppgT*qPBJIMHI zSp6$9WH^lJK_#>|Z2aRlQ94Uf@>zVAGL~ESAl(78LTyH=vwrf^HQX7QyWJQQg^|;+ zo^6S@J}Tr4|5)vgVw74Xe6^OGDLL}3%iL#DR}PGejc}SAKZ2>{Z09r~&UvvgmJc6p zM1PU*8(NjVI<8Xxa-e89@71MG2*U_0-e-{2v;~$sOaklH&QEi>iidQJ0;D}`a$!Bz y1?GzL;%Yr+v6XJmr@jf!9ry>;{(tLvhjqi~D#yMEug>JZ1(dmsS=oh~=>Gxt4f&)1 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-gif.png b/system/images/media/thumb-gif.png new file mode 100644 index 0000000000000000000000000000000000000000..7fc2b73d1b340f1003b8991d471a309116fa2755 GIT binary patch literal 1751 zcmbtT2~$&t8jVld*M`Bi3Q~PIG$S+aQmue(-AEA<5Y&hWl!R3gkfm%1P!L5_7D)&d z6$v~cfwBaU00AVTrU{nS5D5@8vRJl|Eu&Br|Jq7+&rmCzo zh(xlVgCRa&78Iy@^iX~|N*)%f9~zQ}hbas0svbRpxUWUAJFU9 zU|E?WIa%AyRx{`(r4q=<(2tDBV`7Ype`z~dx@XU1AwL7ebmg7f@~hFhSFg0Jc9Tp7 zWM{(*3ySz_n#M-u%^Qk@cpxX+@b;}?dRiV4ZkU)*Bqf5SrSR;mwx?TN$AI6zQ_(7w zxAVc05{S<;uCA&oD&YBfXmHRlIjL!FRnzGZkEed}Uywoua<9vyufX$j`r%<9E7K^I zYMPo1q6w1{Fadx`q0n}8C{j~Ea)~lGSHo;pBqV5>8ujDjKxU?XcnFqAATC#V`?jvH zPd_#a4GaJzqPpgZJTd}EPY0=`+RjcOJx%xGg<)z+)6!zr=~PuT{kXs&p4Kp#aspmm zQ=`nkrLL>fG&d^?3zT_z%DZ=3lY5)igOZB%aQ7#PW$%`V!T^U)XPx3PpIJG0qA}mx z*zX$Ewx@QDqpLfmlVmA4I*DoDOjWsSZ`WT(2dJ49OZvuY<6JiRfIj&7#f$4H`v(J9 zQtDvoB?%`nN&BZT^w{~6F<~^1_70+Sp{Mt5jGJ?}c+2(ifzGe+Tr!h+;OxCkX3fHr zXHsNvdIa`ehDNZW zc7M+9eyUht-@eqlIi0+6V)V?2HB6G={U^)&vOWmx50B#GD;L7wULe{&4CXi+`4R@j z!y(QZhMEEIOJ3iy*UgT8{qbE=$Lrbgje?h24H7$F&US{Jf(faqOU28}&PePF=GJJolazOaXX9AX@hW; z!nfNe+>}CE+0FzUhS6yicZW?jGi-66d*w~MXCVI+)m+8xYqX^@eGcu}>$Gvqt4#bm zrT7!;qLlGH^t6cLe-9)(N^iy)22+s*E|>S)w;v@v=Xj&0Gket0@bu@04(_J66AG^= z18$Vobpx@sxS%1Q>o(^x6V6u;#Vr`+z?>CEUK@&iNm$EF`?aJHj9jTg`*!_~`Gw!fT574+`vZgf1D zhAu;I?mP)4&h2FQeeI8Fv@ex07OO8{+E0+PYmSc*>@~N1H_0Dw<&hozF|SS_U6ZaB zzu(fdnv14(eM2hh+u?TXV-{wfU}Fybv9=dl8_YmrC9wNzCVA@)+Q-M4$i-VTm1{vE zQOUl3sF;U#eB(9Z%br!&xS)oIkJ&yyCAj-Y{D&4RdA#3#eNe6Lt`>QERuIQn;>Q&e z`;hTg9haB$`YoumkKBtF4)oVIu%^YO$qvBDla%UXhWF|lO)14wMpWZVuxeH$E*efXM5 z=dE!e+x)n4Z}sj@ZYU6_p8E8w2l29=9oA>x_7i7Tie?CbkRSQfKPdVCrMYXyO}9ky Ui2D8?!v7|)r~N!y&|x?K2bQ$5@c;k- literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-gpx.png b/system/images/media/thumb-gpx.png new file mode 100644 index 0000000000000000000000000000000000000000..d54b9156aa7417fe0f692ef58ce4b9b0994b0e54 GIT binary patch literal 2972 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 ZT%Kj)lJoX*QL#TTT}=axYIV!c{{uox7zzLY literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-gz.png b/system/images/media/thumb-gz.png new file mode 100644 index 0000000000000000000000000000000000000000..83b10535a7bf2872127d5d117183fea9700d30b5 GIT binary patch literal 2134 zcmbtU`BxGM9;a!m(b9IO9IbJ4oodQVvs%~6h{?(IwzM`=(=;`&Jf7rjR<37iptd?? zWQ~{^9;D)}ln0;)C^rfSigF2fA_&Mh`!CGrz0dm`?|pvzzHiYDZxrL1j%~=1>qibr-ebl&D6NaZ)tEcer zf<^7tmO`nn!cRBRW+@^CZcZB8MSSvwdgt?K_w)jS%AXL*lX@pKI^E3{Ts49HeuA6a zH<>d?`@$97YQd%UPX@M*5f@~=Y~lR!dSVahGh+@rD}~^gg~RmqO^t9x`Ml$m)2xT6_PO* z+=lOEFJSl*%&eq=%D>l!fA*DHN1iFgGN%_;UXD#=qRCwODq&vM$`r_z>USh=Oc#+U zS$fz-9Og?tQg~ly#KjoKZRF_Fuhi&H;<#Wjf0$l^VHS-r6dUTI5&EW9Hz|}OX#75o zuzZxI(QSRA^2MtgGxF8{4$`P%MN}uDoh8WmN=32eEAi6|$x;|| z6Rj#=jZc$BiWd6Z;EcEo$7-PP!cpUo`X*kFvp|cmLN% zGCy`}A+TGYFo1NS*+4?bF|<)l%1L;$Hyc{Pdk#M63$m~vK$ZlhH%)v`0ILo2(j>R@ zsK2bHJKr=EC2^3G@Lr>ARJplhl@+{NV;0~KjoWsCF#qWCUwRLcOM|UU znYg-w!)+k{TBsZxO`lY&_at0CCt&Q4B;8f6kxS;veu(wKu?9&KQ-8$(qdCjMrq>Cq zLnZ7=ZMnK1TtbM_9ppz|JbGDKUGw%3Fxw?VL?5ecJ-#@{+8)_O-M;h@Y#Lc&u75ww4{X&3thBIH62Ui5A1oO{K! znz)ElA4&Iz8V;PUH@%B_i-aKZ3$IKMhu>(kEi@u|8AHx=6pBMF7(Ko3*@vxBsK-BE z0@|l*hT5Hq?>tGGz#`Mj&(s1H1eVr>MPiO)Q7i0)v& zwYh%~5x6tb9r$Ciaf(|U`ms-1MIxoz3F2Q-A`S6EXHgsYhOv#~wcP8NaN{I=&HE^txE4d~>DG?rzVlO_7lKD$~q2 z&ayMt@s3}O?b79jVoRC5HC#8DxJP=d%>{#x2TiP(raLGft$W7ctpsmrp%y}e9wf~qST<09 zHrREw1PSqKN@`AVh>hZAWfD)t#$oUl|1Rv7l)6SI5w%a#ENj9qn>hH7nV6Mjc=CGQ zsN*J`K3q0gI~zX$wJz^@-kz2Es3$C>5$bVozy9ZpqONmdjZ=J~H^^*S_V+i-4@E}b zHBZQ?Fn^fKeM+aZ;UQf-6FWTl-By9dzwSuco?V+G#{25?9xuK*HS;{k&Q{l$#1bYp zgFwEnEl5a>;y{8)4kM<~=KKY?i-0=n0pnZ8$W7wyZ4H{U6@;wu#HA8kcjM^MArSOp zYZI~KA7gmGyqkdz*Su|C-1%F!sv;Z#ys)pPEN-eigEfB~d+BIoW}rh}jf_b{00Li~ zMqnaxb?RItV~ZIro7w0mEK?L<51QnJ?lh#T2qEU5WwhVr{F(`b*E_`^)Ps`QTdt{x zsJ}t=l|H7XM5MFjx*bXsAZ0fhHWc$mf^#p{B4R6@Bw=1**-t?!=1&ZqEjfsO%QI*K zy8orZHjhfEsKSzeQ1TTHBDt@t?h(7YPrINk20iGohBrrcU}kF6*Hdh_H0jkZyz{8( z1=7aSg7Sp4C}yfwA05aLUVj36_J&;_5bPO1-5@pfiRZ%(6dX~7PBF8C9d`eTNBRFL Zb^46v(w?8mbc)#@+0EJeTC-C~&hIt_seJ$d literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-gzip.png b/system/images/media/thumb-gzip.png new file mode 100644 index 0000000000000000000000000000000000000000..bb7f0c66c49bd7926207c091cb93258fb88eaa79 GIT binary patch literal 2451 zcmcJR`8yN}7soBrjZ{~ZmMcvX>5^NN+vc5W%AgR+ZmiitQg*qc8xxhWE6ZfbmNl}E z7R%U%jIlIXvyL%j#)KK;y!s>V=XuU^&iOv)dA{d|^TT;+sIR?a+ktIDLP9%qFlZwo zAz{L=5EI>0s_a@eHWR}6(v3?(LS@ks>vmfk$~4P&Js;KPOh z1UzW8gpGAz(O`7}$gZ$F2`x^*XoD6-~ff4|eVBm}cdkoq0e2c^TcAGyW*VUx3xYM^bD=8~n(ctU0)uHli-yr+SY-i= z3M)*Q{si4PXto7<28kN7`|6R zhYw8F!&C!|mqW8XbpHeMUtsDZOw_>YBJ@N6cLu0Y(CH6dAu#X?I{l!{0~Q9L-5X|` z;pZ&ObpZ7#wBG}2B#f3o4<2|N7<&hc1F+B!L%A^h88{Qb`3|k_z?}tV7p$?N_Zje( zVU`T^Oqi&J!4&8WfRVS*<^^4k;Ae*;n|*$a&0O1Y*}sNeOw|Mbb*_KFfL1o0atS zq^G;&&zEb~`7Ky`o*=G7j|?riPn-KXe>h*YO?dOl{yX;N1Q`tbWqH)}af1$(oULT~ zsvcou+p&G4jPioe`1lfmK5zcsZiCQ^$dEB&4eNy9n*IrY8T^FCixF$$RC%1WM)8_O zrkX6H7i*BHg?U-SGJo-{&Fw&&pjKXP;@D_x7GouXNOnPb6;)r5bM{^(j@+B&@B}Ai zTSxm<@VOTr4Kjx&<^$TwWbtZmk9jI0+paFuTHQOID7v)PPMtUs{c!K_&(++>-P@2L!jKAs4L7RcKqgWQj*syxBeVW z&g&Tq72HkPIT>fGG~r$}6Bb;dQYpH`Qa>BbbUky?&6NLv_WZN+5| ziz^Wh9;#Q@DE%wTHja|x{L)I#gk0zI@%~r2>#D}DD$F&FlICnbD{_nEQ&Cd!`}vQp z-i*cF`Ju``z#SZ-T8te5AR3M zJgL~5atcLx?S~UPiRB&1*E~b83V0nhbo-4i#g~6&ux|ozI!DSZdMoS5=eq9)?Al*ZqaC17 z|2x{uC)DWBdXh#uovl&_K>zxyX2lI(R^@4>G+6B)@u`Tj_|Jc0R zhl#DAb~0INCLI4%*?iP=E%76{P|Ee?OLEE~BTinGnpBV^=9VuZ0;?F~Rw_N>DR_PE zQ{d8bydmE`$w4H15c*^LAQ%N2&9W5!oNmBBfVE zaxt+~p{g2v**zwD{%&HIQjiq>&yZ`=H;-FClpsks(xzwrc-7}_vz2P%;)rBsdp_`& zX%&6HR8>n&BIB|J@|_A7(*mb&AwwMtqzi3!kx;tZDOWJk8CT@CQ2lRs@J-NagjbHn zqORWcH9M3D4^`caBb_%5s6IUQkN z{vfD{k<2|onAfuVi*b0Z#0GIHr_wSIgH|RKRvIhL9Imve^J#vJ>e_TpWy+t!O$G;O zCwmZ=XNuE97AfU}Q@dNUDZlH+qBTU!AIxP z*IZ5MR;VzItVZ4_k4&X2&5A0&Kbx$U7$}Pg!#hx~X6H6$2xLN#N8RL1t#2iWIku-u znpRY~iRu_~s>c@&UPPQ~Rx8r;>LX%*x;vZa_GyYwrOF`KT2 zkLDIOu02PM#0+$=wuEUEQhMX{8w@#rYbuK=Z10E{8UEL!Xx#;ulX%)P&DJUWT^=5F zr!%+@SG;!9qrbt#*lNg&Gwyx~Ii7Dax`$eC9;n{jFjMR1wT9=J>&5<|jIeh|7`8RV z(zUpam&Ma!8?Tt XW32+(KlmIL|K+4}RUch^**5I|P00{C literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-hqz.png b/system/images/media/thumb-hqz.png new file mode 100644 index 0000000000000000000000000000000000000000..2589c16e210052fff522e07461eb577677f33ae4 GIT binary patch literal 2604 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 iO9}ty!Ts>I3go|d|C|`i5uJ!L-k z1O*=Vlbs@CRgW}6fmyFtak&aGA7!!3X0zhfEkyxSJ3MTVNZ^Ec<&!7+>1l0WpR)Qf zR#{=vYIWmd&@UHMO-`WMP^M))e)TGlv=ORril@W&F_+O#$ zGcc73U5M6AOdwfTwId@Y3^SrA9D7j#f+medClYCdLYVrSVR;EoPJ)wY@NbFG*$9kX zsh^!y*4HcY^N_1oFUxB4(i2XS?Foq+qY2kd6QlbQKB@xy--9rT3n>8tu@N!(0PjdG96>H6?wOHqoXFZ z8m3Xz?d|#xACTN!6_=x*npC&9$JF)=2k61`if zn;6%>e{Wb?Qr6WOWit5JIPB3QWo?bR?WH_DUDMfVSX`86WEke8@=Gc3#TevTjA@Io0DYbI+}VE z!?AvNiQTG-|IVP=X*>LxC(v=;n9=;rzWRO^u9ejhK7gPS_$#LzJ( ziu8GOtO9Qb_i!_QQ0I4dRaSL=WksrkHXA$jzNsClexMWJy3eFaY_-7BA2#tR<=Cfe zU$FbZT-*kBAeg^I$J|#1)Z7T<;ecM8UBo-TQ4?W9I^&}x7LyC6^&Xqb?1Vf9(J_#OHHMtI+u$<|ohP$f(SPc$6vf5IdcP$8R};MaO3$2k&D={< n=8Pm=7l+~g&Bw3KYRtOzC9Oea&EHn+T=fBdM|>L&on`zVFA<`m literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-icns.png b/system/images/media/thumb-icns.png new file mode 100644 index 0000000000000000000000000000000000000000..4d7ac392ee88eab62ded82308add3a0699d6650c GIT binary patch literal 3212 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 VS9*H*x43_Sp}w(RnXcXQ{{gDb!N33j literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-ics.png b/system/images/media/thumb-ics.png new file mode 100644 index 0000000000000000000000000000000000000000..2ddc380f32bda47b1565a040e844b9d71bc6da2a GIT binary patch literal 2787 zcmcJQ_ct317su5qooLb0k(WGA`?RG`YgB8zv=miUN-0HcqLkXJX^b{@)ZQzx#f;c9 zB*Y#;h>-|l2bGp2Ue7u2zwq31?z!jt`Qe^>zUQ8M!(YGBJby;)3x7`Kde|q0+d)lNjf0~q_AJwi=R}|KFwlI;fgHR>ZbntZ(-M;+LXVG+*H%|@5@Ub=`W5c);p8tk{dwRw1WI59l9wXt5Bo4LEQv$VMI#l>!AiQ@0{ArTtP zWFE2E>hEX?Pfhsl>%PCYH$5@lkH!4l-#_^I^Sh5*9kOJw7mF${ z#&)#!VLBl0pstq2PfP)h^W2r5v75fv0(!#R*Eb{zZbuj`K5;#Yk9_;NI!1avu z_btwmC=2sM!bF&tE55s{sira~5r#zMrbK-!&CQ^#EYD6)<|f6(eDy2I$>?rvYO6;f z(v!N|n#=OCkcgb|;X!;)*X$Ib4P8rFnCt8AEXYWnpCR^QIyxHBXk=+&W(s+ploTE^ zJu!9@RA|V-ahj>Eu4d><+$1q93C}sO^xKX*g0}p(0o59AIb;sC?(_AGxq$5vQyRtlRK+IRM-j zPnq3!10#&hlqbU)3_r9@Mkz44M?ndj$W5-vNHr#5`G&CURhR~#0OEPoIzYTxb^dk7 zyO1-SmV(fC08-QRpQbk^P?u(~MZtd>^I-rm++1C;@x%aglu#Rdj+1^4s6vuA&Rc7D z{b+5`0pO(b0o|dSSj9n|qqS={k9^Et0FV%1tIH8Zi8i~3I?h|CIs&xOlods6aSCio!;Wfx< zRQ%-8;A=9Q-&tHEKf950dt1tIP^Z{qDBNk7>U9g zFy3xH^{Y6y-^rFp#RkzATLf-TlqN8u}|Yn=MLy9EiF`nOz^@d(|Ugajx> z`lj=NrNmsHx)4+X_OMt-T&@El;S^}+tP7b6>)R-sV+FV~vbgj3mbhrfV0?L=Ke-Me z#{&-M2^epR^`Jevq=mAxez*4pDTfk~A0(V_2`yzHLAS5G0t~7?(Xr9(-I6nkFmour zzvjKr&7x1r!GK`Nm)QhS%$h$hL_72Gh8oWkK#>z7(FfnJ5#mfcwNlz$}$VsI$2DM!z_y6 zT&ir<%}uXb$=<)KXmba$$~FB*QfhgsZsDEru#H^-UVBvNp;SaE=evUQcMp}qAG#;0T+->GQ45`1J|L&sn0E; zVu%U|g*P-q@aLqrdrxe&g`0eqGn87^(_#3d2c&@l4uJgbAMF2plx^+!B#69KQCA@V zm4uzEvh{?D4<6co#hbwy{1`zTZ`IP4j3blOeYy$s94*AJenCBrQ*5UUsC&!=9 z9`-6NNRhuM9+J>nA*o#amTZzLX!@Xv1 z2}aF^BJp%tTnM69`B=~LSd$r8#ZJE*x*JFL5W~IjbM1U$F1LHuP)_u$)7H^bR({M` za#9s{o-b_=zDU;YS~~T!EA4KG@|z=FUTEed~KlVNO>gj3=;#zpwWNs&Q3C7Kc9=cX_jwZO#@SlEQ;t=b5~A6gjyq z;rr+t{)eglYqljOuX|lY`$~ zC`7n(3k8>P*+vj*1$fJZJEBFou`YB_>BLip!XeVq=^eNAHK-jGoQ|Y*6vR9-HC}ig z!5-Qm$`S;8ZN0c4D^Gv9i~0{G>KRE^TuynEBD;DCyZ%kzgZKS$)V<(3u+3t<2mgOB zyUZoPlsqt;$HyOB9AkGPT4-$-q|?mP{%#03Mr0}!{Ch9-|D9){uaV*er9 M8n4umPd^0z4{2JAvH$=8 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-iff.png b/system/images/media/thumb-iff.png new file mode 100644 index 0000000000000000000000000000000000000000..09a2cecef34f29850b2f38804cc52624b64f30f9 GIT binary patch literal 601 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 HypQ}J-ByGi literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-jpg.png b/system/images/media/thumb-jpg.png new file mode 100644 index 0000000000000000000000000000000000000000..b2649a89aa8cd3d53310fac13aec6e1572d68520 GIT binary patch literal 2435 zcmbtV`8U*y8+I+ZC32G%#N2PBMe9|TQYtdmLQ0k-54DSLpAq-3>TRUhu=O$hg#7u zUAR<&&rQJZp2?tPq??Y$4QLAdd6u=pxE)(t-QLi-!QJU^)VF4E6H zc`T%>5^nkj>83&D(GZ=Ca2eR_7fd(=U1ap1Su^zA>5MPpjFD}5X1=s`^n;k_z zy@l$N!Llf@;4+vWfPP}aO;6Cl_vn{SY?=?|`C_7Rd|m)E)4*5P(UEo>SO7Ad;rChi z+$30f18FM)b1y=)crf1|%)5k5eS#`tU}ictGm7-k;rH1n?*ml(5UNeWC6frZ9-`mI z#(L18ph*W8s!unDrcH<$^_5pvqfdX#`aB;DdZpgPhz7$kyuEnP{GP7>MPb z)K$K?nPtB+nYQoXit)969@CVbm5qt(O$pMx#JlRL_PZ_8M`G*j86&ZKWFydR)kAdN zQWd*>(Mp2kO)+}+=LGKR{9Qj)@p~};!xXor#^#vTmIq3(PG&;rM^d}~TuE6^?~)!vqG5BHylxfX zRT`Zc;CXL^#igkzrk*EYIU(C)pQLg6#h1oUDW!T75`{MHI{y6HDx!Wwa}>o zKc=LG@id`spKjGr0>wkYc8aOKH1#gGWu-x2@?g+=h$OlHakonTlx1oNC#Oto9n#Gr z-^y-Ep*o*mG$wO(h~Z)G2fuo>7k%3l7gblm(bjDGmBLf7J)$;?M^=xW;db&(p72@s znkpB9$U~Ndht2g7claqbCi&~nZM<_-CoNf>t*W>{bO+d9mSZ?;{9O|zw6HVLA%`6a z)yiwLtavMGZ2TxwX?8yOhDM>9L@{}2!@%RsItNc4r5w;?5^dZYyuK^<8ggiO+@1MhYl4%fisN6!}UkV zuURTl>8i#JTA{iX6|?GW{Eg7S-j1Qn%VoK5hHsiG3|_mjkYZ9Jc0Av8gjAVtHq=xp z{p`JEN3;upT57)Oz}5M_v0&e^_q~3j9ziyvYl&OQ9opzMd+uHzg2@(&{W(1is%Eu= z2HWxHi8r}s@21D9l@-G^>a+t&}lUee(y0M=Y!`+Tj&d#-VY0Mb)GJ8`*W!{`8pp@UEEE`P)NPyYmxARx1QO zxbSGH<6hEaS>oXpF8f^iUJS zQ>f`gUoSAATw7{Q$Q$@YxO3h9mHJIxFQZ@dQ!~iEVNiQ9{_ePIqS>If+63tvVc}g> zf%lss=WPV)L(QaeP#6yrOJoRxM?(Y^~=#+NurrnmC?2M{i8ejgJ%Y3S=$E6Mcy* z$ogfj_8V4pUf4L0`8k6w1gyVFMWUU1v^V`07BXql>aR{*i17#nsykz(c8gt|i)RIM z@$BZ_1ToJx?r*gMBQ3~i(Q;f+Ty9a_Gei<*pMV;UMZVx64u*e=6sY}P#cd{L6f?2g zZiTp*jq|Z)|IJkW)W#YxA{!wbTEup63@o;(=jases}fx;U4FdUsC`@|*a8*I|H%WrJUNa}=U&+$#%&Mv8foPMY*^3RH2mUl zVV}TbMy6CRTl~CG*SIq(?sY6n9a=a$pZZ9gOUe^sJ35iyj>{*J?m9<-mQo6~OrLK?=3Ps`8wUlK- z27}R+Z5U%2W0^6l8DsW+U*Gy0?)RMUdA^_Lhv%o~oNvKJe@_E_6MX;xVBqcL76<@r zDE(o*jo&vtF=N{AV^gfFpDO^+Q36!QZ2rz09t3)V0O0ApjPIFu(f6|Z=<)-tRx`Ss zL}vD*SKltI*36O0bvm6=Etw&{7{es1HFA|&HcKoU$E5Kkgf-3xh5gUu%44yNMqqTI z$gjz)0XQ~OqftuajB#wbNVbMq`?y4{r}Gdi>up3vw_M3rYZU9eNv%#hPp+0Jc@rzy zNWybEZwi5Xil=vuEk9hM{ym0C(Q4I;l)q#O&I+v+i7%KTmPi%s1xhU(n=wl)Rj5Qr z{PRW1dj@|RfyZvZipkG%g%N`!q*- zMWBBnGJ6ON2!Y;(r*}-@pUo0qqR5phwUo%{!P1(QD$(*<1A4W3d985*o5>W+N#yip zYW+HQVwzA)=Oc0S_9=XxLM0SPh*b7B%v#d|r4GIN4ohpVCQ(kC!>i>r|z@uCnd!YNlQp; z5vT;>V(0Tzwv-byG8{yZCazRlVMd0B>))i9eGPx+lScDALHo@M!S(zb6|eO0vF`yz znm(Jno$gkC!U#n)ym84Ppb~0me>b^&?p6%#A{^ zJc2V$3+XB46lCk&-f}X?WVh{1voWgOyjETxteL-8`w#X}>sQJ-ka^JNFeGSfltsVd zoGQ;16bnEedR^7IBR5OE-DCF=i*yxR^}3$t9{x|h$$$OXFE}q8JHtLKSYMBHPWm-Y z^y!r^3ukx`ugt8_H_aNBzuYQ?f^ z6eZR`Xfg@9Xk`sz#ziRpw8mm48Hqz;cVW=Qc#pIxCGpNqLFKMka~v+8Nvh< ze|&pjU;udV&XI>h(z(UCx~c8a;9Or!80}CxWe==Tu@`tyeEkZRKiVU?djZ~3`mMjJ z@~tn<(fy^uF!0UM6F~?3VB1VY2kU2F-EYI;PR7szy<(^rcJDkgXp0QjKWJE)9NTie zx}+`RduATL8Gk+J^VJhZfmY#XWf^`7k7VGn&!D^eEc};ee*>58E1-qK4Q$S2TKnwp zi!zR@&Bb-8c}XSeR;WZ@_*^+m`(-fgc`1;Y&t zPSyGJ)>{eeJ<10fXp#C*a#@0Vac`nyd9L9YxMXLi8+E~os@(YG3quuUY)!Q-3k`$= zJLYIdk!?Lu`*#m!9m>=Je~k$UWuDUe||I0yXojMa40&vJR~&1xy7?4?yCZ69W-cMdd*e7=d_$nEXwHs zMe6Rc+vI6Ds^vGZ%8?bdL#I4b$!<7hZqxMpdEk}=X_)$**E;yAXGHQyowHK}X)R-K zfK-gSia0-(bog1xX}i?^oKaTGc)n$Ge7=#*&qC_st;dZHsFp{#;QGyp+1m*0ny0rf zzABMr5i?j-=L`^!LTrv?W#&vAd_L-*%MR4s%Qf z3lE{GPbRC-v4tX^IVbG2K2Q<9h-sbaa_*6fvLk}rGc&9hMaEpj_E;37*}c%3KCH~s z-?p@Z7=xaVU9aVIes!S(;%=XnBx;}g+`W5OpFdoB0WsBc zfyRS1?>N!mZZ3M^gJs0h-scZiK*K o68U_7lp?yOiT9(7|CLfG`z`d)$EHJFul=~=?e6ae21Wn*e_pq2`Tzg` literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-json.png b/system/images/media/thumb-json.png new file mode 100644 index 0000000000000000000000000000000000000000..bf8c903ca3cc8d3d0db0062db0b7fbacc0ba6870 GIT binary patch literal 7818 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 h|E7A1D_5}z{XfJU+|mF5 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-jsp.png b/system/images/media/thumb-jsp.png new file mode 100644 index 0000000000000000000000000000000000000000..c45ac8ecfbc98f7e0f4be032cc3486b6c8a964f8 GIT binary patch literal 2498 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 M9(Ay09=SySA2)q!HUIzs literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-key.png b/system/images/media/thumb-key.png new file mode 100644 index 0000000000000000000000000000000000000000..f1826a3870d18c7b54260f203b3b6b83841a1f72 GIT binary patch literal 2130 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 F{|C+pi`W1F literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-lnk.png b/system/images/media/thumb-lnk.png new file mode 100644 index 0000000000000000000000000000000000000000..2113331acd46d0c925b6a4f8f0b0d8d58fcc0393 GIT binary patch literal 1971 zcmbtUi#t^5AD@09BO)nji?h4RQ+k@s?zXi{?b)P}nrgB(`*jB&ryjI)tZ}TpB|Ow(DDOMfCR!{nQIk3+8VqnlzQ|RLd~nK3k1Udj7}YQ zKEgfw8`sTMtxzDB{IG~{#P_0nVnVG}Pi;MS{}%YNb-GQVGk-xE|-Y=iF5i z3F7U=bv>)(^Hm}d?^?1FMG;RAl~l@gcT+4ZD3_PzA3h)#&Ldu)DxnbZ@fOzBD7jqZ zybnqbk_`=^mjltj0LA>gN+3`oh)OP3EHCp?t|>7Li;h;}I4>zlHa3QaFtE5-EGbdk z_DKBrvEW{bY-AXrT~N%-@K{VKha-8xR>@>2BNz<{k$?QiOZ^=U3l%jqDCXvP*HaXq zXJzl+BL0`K=qP?+0WU38`ueqEX;Je0x#YzQeqJ8p=ZmM`M1!ti3GuwtR7rO??`jf$ z>lU7oA*?8ubUl-Qno{z3XfR#;^r>QLN!-zahKHe{Ay`zTsHH{J(kvSu<|VTPaH+Jv zU;OMD784`?^hw(PQuKEdKRa98*(oR~5w}0aZ=?yz{zNX)_yq<0>^riN5nf7)^vxUT zAP2v5TQ)i>tgS@@{N>ZrviI+WI zer~R?vQqq{6N`_N4h%@TxyxS6yDcTAZvgL~(~@_5MLrFez5V%*cJX8^`tPQ`KYcdRt7W_+IT?mcBf->505dI1FDanrUzo(;7AMo!Qbwj zSP1K#?8}I3ibhvGh(x@`TwHxS+pGU#oH^Zexg3Z!vs7i|hBO_v;QuL!Y2q1)zgrO-_>(q8tL^*#c~T%g-Dnz`Gq{SoFv zUjrI5&wX!w6zVurx%BD~NjJ+Fjv^g~cpFO1i?zT(reL`s3~~*;Z@9LS1*C#$b34AE z1v7@Gp8@0$=+oU7+Gec}9(_ET&jP}kjeD#U&BykZ1Ag|AZb5p?A-%zdnm+~n=G(;j zgNqD%BiI)T4qKR0O)P*Nm7@Jo?nZC~G<9wEUisA_AlKfGqTv`CC+sGL9Xk(QsQ3YP zn_B}?naQYA?bgYBlbP%DDb9>cz#n|IZCAS>gbIzAr?^r!S?(kYmpiSuv(4!->BKA( zS|4}z#%z2Z)1+80%fzy3PjT=b_C|OXg&KjWYTV-MOqw3GKhK&>g<7qhYJ;}*o51@i z5DZME;{hi>>ro4~Yf`5=f)W(OR%XAKr3N+4I#w98GED*b`l!e0}&?`408c z>~Pj}G~DVs*63dq1Q{ewI<*30-~6f}Pd&x64vp&Lm=`M|H1}vJe3F=DYuUN^ExE7HV z!6|Vm1#|k@8)%+!+dm)Im@|1)lPImOF&hfl$7ZKSGoNQ}LmF6a13JXwW?U<8V{v1V z*?Kqu3Z@qUQ%aXy{ebVc(VpVA;I5NWq>bfvVhu6VPtT^VYCa9D?qMbOYU<^?d+aS6 zj)RUIj%FsCrUh*t3OGoP`t}TTwVFfvHohDXvToWHYD9c<(ErNYIi^0P+MoNuqWD;? z=Mb_v@TK+5oP91i^q-tM(` Cg7r!O literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-log.png b/system/images/media/thumb-log.png new file mode 100644 index 0000000000000000000000000000000000000000..e36eff13b05c1a08431629169d2dd757dfd92d43 GIT binary patch literal 2762 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-m3u.png b/system/images/media/thumb-m3u.png new file mode 100644 index 0000000000000000000000000000000000000000..a7ed6c326acfb43248cda126d118a18a23b05592 GIT binary patch literal 2909 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 LWK(_aFUbD@7N!=3 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-m4a.png b/system/images/media/thumb-m4a.png new file mode 100644 index 0000000000000000000000000000000000000000..224f058fdb603487014d5e8e2e4b814b4489af73 GIT binary patch literal 2754 zcmcJQi8mCA7svHOWy?}{Ma+}(T7~qp#hWBNX=Ew85=~^u#8^kn)Uz`vLzt0c-VK#l-#-=y1c?is*WWzr4&H z9p!!fN-ruBlF796^xfTET5b-jxtZ10N(u-dd3)`OL{tPqNT(CsTm@@uy8^+kP$=SZ ziSBMh4|gGnw9Drcy*x!MmXJmx`P~=sc%+A5N_4b{!Qf6#l6<`R-@lQ3?~y}-IRgVE ze?R`upS;CI&hW61NF;iCkna1ka5(PFjEK({aX93lNBkc@sL4sRtW4_jXCgM641ut_ z-;pE21;2g?DHQ(73N0;_`~)J{-ey$1r6IHEd3oINaq^=;(gT0Sn^H0)jGB}5PWOsEjN=iuJ2c)|`%<3vuTN^DigPM}eC@*8y)bLkV*}c7_fQOt9{k+8ma@b=b zmCC{61q1@KwwC+p6D8p(XK;|ew8UGOry-G~dw1yt`P``~&d3M_3T1V4FiJ2C>>KLe z&)MDG?Dy|k?d|NIZuYx(l$aR7#s+s{f|{B_fyD`_6n0l91B+$W*RdKKSsgg)GX!sT zmOD1eC@m$2hEO7*{BKLNoNQ)Q6(u@~)zn0VCkocrXt}v`bTO-?MXn`kOaD&-wU~_9B;sYv(O2Fe@tsD`Bd)wGXC)sAijAope8-(P;H$whPWWIQ)Atqqcf0rIYQ-^tPisbPJ}?jz6c zF$4xUE3as&apyGR(l_i9d`$PIj}H-y)Kj#qvr`+GxBPg(SiPwrNg(TJFks?)%iJG1 zKB8w6@{CTZ^+QxVpHq}^=>)LTpUCN-V1UY|CIHJG*6FsY*q}0qoW5DO=`!!Ab|~L6 zNTcuqUU|e2UokRC^jrrkL{$5~S3Bc^lp8U|f6J4*vI<6?Julv4ql^u1ibW>o$=Op4 zY{-P8w&3KC5Im3Blkt-YmLNuqM@Ciy|eU+RSGjax6*6Bp5E zK=7jT7TXUEP^32#Fg;u3`Nh7eSAZ~uj%7FIa%Gu-j2lM*bW=3R|5fn@fYuI66gXWN zzm&VbQiWeddzyMPVVr)_24RGYo^*}EnA4vF%<(1C++oLQfP9K!q={ZYasDM}vJMcQ zvAV%0^s8>t*9Q+&ZefpiTsD%(uJ`>u+*}~?U4;0+@uLLLBD;m#K?UYLOtVnXmT%J1 zq=hw^Nxu4FhddQ#7+D2d&;te`+ebdlh@Js2D4zWu6V%@mZilZ(`7UKV2Hv|xR$U5t#Xe8O;Py@BTH}uMnJ@~>MdAB})R>abjJsTsQb&%Ey zO({G1SX$rP^M~`JAf58pm@hBMe{*`V5$m%LWUD+D_2qak9iiDY|Uz42re)~-3{yHE5jQ{v)Vd(Gz-Aqbr!kCwJ ziKOSMbQGHeefIf7gPrEZ4pm{xI83U^{zmbA&lhCWC7Y|=LApA( zqYHI_>+5R~N?{Y$^?)uwvw9MVp`9FuY^Z}~ihgD-NV=_?w1CEQ0ljI%6Sbe0Pq{bC zKr-m_b!Z8SLa!?}PyuQ{K-z8&~Gf|;{RCQaMszuV18KldcH zu?oy89bh_tx+RwkXaz>gofGPd1Ursmharlz6duW~IOTI4DZ zf)n~N@NR=-nWomt7bT4nfF2vQB&1*7CjL|YcxaHmM!wf3J%ONy^Pm|*r67`=noqy2e7nl=D%~h zci=aYcNgK%q3=&$>i|ioNqQaUngMJ#P-H66aS-Ght)TsfNE)*Od;nyb#Hrxk8z~o% zZILckz;!4J)?uOS3Yg}9oTZJAr#oKX-5P_dBRzQfF<1D6Y28>Q>@VNt?LGN^ z{Q2Ny?+K?N8IN^a|QW*N3 zi(|%1=xKbu*n6;793dMK=lKaobQf;gA}xJM;sug=EGem;b;tPnUZ$zD-FcfQKBM9# z$JGaLCd|a`?L+*K`W#uUp7Jg382q=aXchEg)7PMP;0WE90PD_84MT8ypg~rFL3>&w zLMf>Bwx$CP@fKK9@io%co1+7{G4U_{&HtCC^zu{}yrDr~`(7XV2iaQPyjFMhF8qIX CC6qn@ literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-m4v.png b/system/images/media/thumb-m4v.png new file mode 100644 index 0000000000000000000000000000000000000000..fa45046c78440f8e69eb789ce25a50019f768a4f GIT binary patch literal 2738 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 F{{rDTe^dYf literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-max.png b/system/images/media/thumb-max.png new file mode 100644 index 0000000000000000000000000000000000000000..d08aa823454016951b4a12d1fefad4dec860ee23 GIT binary patch literal 3213 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 JtTgyN;(xACn*sm; literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-mdb.png b/system/images/media/thumb-mdb.png new file mode 100644 index 0000000000000000000000000000000000000000..3646fd24338590ace546f06373615fa6bf432d0d GIT binary patch literal 2691 zcmcJQ`#;l*AIH_Ph{(6&xR=kPT)q`aR9{7%$z_PpT-Kq|ndlgW@a;HCS#jz#>{4z%NS;y&g1(xoY&*=e!L&A=lk(^{q%Y~-f7-mt{Q3vYD!8< z8g39LUnQk&sDDQFryry#gs}NzDTjeQ!AeSvS?U`hpdWr)jIXPsQXR!eq@=Xn&HFsm z*{-Ac3x9lSWw~i`JZX?}zN;;gLfYEeLVoX$r}fRO%ItA1%!T>FQAYhZx0K7uW70Rb zwiKJ2n_HU&qa*7I#ktPbGq{#h9nIH!2@bfH%RP8EJa%$fy1KD3GB1&=tzPZHzaJf0 zSYMOM*ZsOXUJMU<6WW*M@{P^STqb=@u|c1o`@mtzHa1-G*k3za@uI0KJ$Uxw0)JUb zm=?AOCO%G#CDJJ5nd!y#_1RTf8hwB^H(ShMG8QDz?vB10@mX9;0s) zu~c##mF(HocB8MGv$TMnntVC(J*K~hIyDkbLM;A78 zc(9tsuHvz8^$~CObyH@=5foy^FwKk5Hovy!g2yxqCc3AEFe2_Al{_w8`i<1--i2)u z@X>r;5u1sf;x|r=309V~8Q-hM*f^o!;XvktZhW<<2PnX#os;mUF-nUKpEY8Otz$z3>+AZujs zE`?Oi9rY)6q|<4?le?Vpn1Q*Ox^eE4@6UCSx57P zjB+0SFov>NAX|IEe}@uSlHdZf|Nc|he^DeD)uf%TY-qWQS%Ew4Gi*}0<~+o3W48pp zQqIfMR#nfgMA~Myc=FQziZHnEY9Lm=v=V7kdx7heb^Sm=V0ZQ4!+udV!}AYRdQ{V! zaXBhIdNyO9fk^Ha_;WoS8ls<-9w`|pTH`s90V@y*5{d} z(j!(N8nmMnv$k8^V07XmvZogNb3GN9OyRw2m7GaEVkk6AtrGz@srwAEZY~B0VQXKC z7ee@P&l@mDja>{WKafiAoPMu7a3FNYjVnf9y70E3fok<1K$5nrc6LSea^H-^A`m3O{!k_GauU z`8aggZrC7GU&?rjk}-!CkgMD@U8B>!6D$XmKL1_&n(<<>^1|Ndl`SzLj(bkWr6KK) zx6$8TGO>Ylw@;MMqF(|1a8PT}jo{peTE~m;9daG#i~#Fi)JI`)V5mhoE$(p4er2D6 znkq-6R8#LPhZdq$dg%HpCp;ARBK&!Lc3>I z@lTqomN7<71-ByEy%jW7@Dam{KR-DWDq>(ov;hJciv(__DVpZewMvSthhSc)G6FVy z)-8Vhv25Bc>6>aX^V071Dpl|f{fovuJF`Oy#q$NfeVxU`#-B3>Kd(_dx%?kQj^RZl z2xOgA%l*XF$$wCvFm9H*EUsPhFEN5ATSMc6ytO{}jTBiOeA$`@W+f!yEO+V(Aw}SFHiqd=L>cfsSZ(b zfKGdM8-ITF&ZL3I_Q^;OI|EwN18giKf<&%Bbcr zrg&i6N@U|Fz@bmn^Z`^U!g|BChOF-I?2f}u7*Y@!w&lhyi5mLcfNQ8^g0r2Kx%|FW zu*8<=5Yy}-)xDPxTiXhNUTx1WNSMQ*pA7=+&b$Su!2H}T%va!MOH_@7J%w!A5TBy` z!v8e^5uN}GLiPTIWT;^BgQE@Mu#x8t5U~A)2aX7BE7@iG(%KkIGKf>TgA7cU_&?oo z5thk7(2$Xh;Btg}s` zQvhnzIR!1hzp5yoi_LbxAzmAcA6;#Wt7ZsK5Kk>F^rn~U;oD*t&}e`VuZi)iJd5cc z`twi|STNmw`lX}RP{;dQu>BUqkO^9v`=oM$6Uh(NCzxMR1w(CO8nPMy0S|jwaH$|5 zj2R5NVYt4K$w!XbstiNauO*@yDwuK$?uBm}i=URa-?eo|Jt!(q4pp}9*N;dPDh9JM zk++Y9-AezU47*y($kV~w?=NgLy{qx%9nh)r`qMdR4zN1^mW75 z;?=-40n4Q{1T-a4%Y+<(QW(R!9o&mS44v3)4+x5yhEzwl|CdwrW4bZQ(wB8vy9x9>YTv6Mqnd6O+ZersIHm`vWl zUEHj>m>aDP&&}3{*GSAUc@&^pZ7+xvXl@HBlDva_DYJ$wJ&xBs2M)N%%Dr}nYQ(M>b@s5GdM z45a%%Ww$_)=>CL>t)wTb;IgNdDp7jy-^%Cfo7^plIfqN#Nt(s}@d1C<-rZ-CL>&9- z!Sq%2Wyzaf`;H@bWi~zeb_TpCmrdK7#W^G5^MgvIb zbo5z?Jk0bXfBQGjsrq7;nTw^1R@rtDwPU;dmpZT0<-QS^)KL(a21B);t{mH0BKHBrkzkG zxu{eG((p=!W6D>~_(y??zzJBPLSf>|tTM?}nQ{yp;$XrKDC>fXAe6~Rq1R`y4_(TX z-w-aHP%dJl@6i4lh<=_}k}1984Ntx;nSN<2D5u!bB(C%_#{u9hjfXk^+P5_i~8ZQ5nSP(<` zHzDQ?DDy1ZQH1tZ5(}TvzACsT4(+Rk=;u@zg48}$p%BuXj(;A7vi*qV1+=q71usLn z0l26ieiMU@wPGVpP{A#z=r%sp3uT>$3IcKI6qM>_aCu+<;B?K)Jso+)T8m9BnVe z#qaUyA-L=he5w!1zKjhw!X;tK)Z+;EHC*)w%JIhr>dx@^m!g(e--|Q-+w_pr)@-6K z`@!I=q0-qY-?l&~CG_x%lElBa$lx~>gPtz@8Kk@bi zc;K(2=>&QVtkGoF4^_1PdV&dPrVm!?X{Pm9An%Es)ed7;0!g@wwKi&+XUv% z)ZEbS{f2SCiqneZhtl$sIbO6uK-eXdE6%Djqe3cyYhagY$Na6|+^Fs)6FVqQw3+F# zb?kx(b>=&v0b?uAEzqh~Pyh_XJ|pop_lI1+{2(ISn1Kww21bAZy%e!5FWiKI+y`J_ zHd!o;Z~%8m&?YT3!*o3O?6iAZwiHaCnZ1A;t=sK)YYI~)e9!9;d1?HozAC&>ayR(a z71|VMRj#lc%-;~_H(|0~E3iSw$u#|wGspt32P8)wIblgzGiqfAICrckM{wy9%efLK z4Xwvk(Ue#czos;3`F$B3Y?>xX`n>miN9oc`E6>Pl)6fbMI%kNFfPuvaI2V^U3B;wV zz@{`YSv7N!+TU`c!LE;Qp+S#%YyjH(gjXM-AsPLj1&P65ci0rt$X18Sc z55*zRLdv#7(bS<$o@7eZ0jC}X(`;&)wjoZzT9J&Cw8lR}zbvlcdtQpx@>$Ol(LO?!oso{g{3CParI=c) z0&eXOrlO89$wFiKjM<>c+Q4XQ*lGkQa!sa#AKfzhw)xHHC&+Y2F;_c_wG2hio(hnL zVbP}5B}1)tmK#J*=0+?*+YMp1reSQ@X5#p6UiUMuIn@DlH7!Z-UW8kc6auzAACPZ& zGqVb$1Cwb8kN(mw1-jW9m~sL1;6C%yGSIBvV;U|he`qn=9zyiC15WA>V=Uw2d7jY+ zWM+yomrQ?}Im@C+egD4OSKfq5`>alv$gG1cjLBY+cang^w3MtebY1+w&Mk4{UVABl zt$Z$(3~H#3Z;p^O^mLoX8$Tu`Wz^ZfP*1Yy`G_&i$kj!Q_m_sHEluFT@?8Bi@2mT! z_dyoEA-GD?#R3l|W)JCUCu%&jo$|P15BcX!9=N~+gmGfm%|?tZJRw~$Ec^*6vbI*^isEBAy_PB4P&D!F%v-Surc4Z0jA=9CA zCfbQnl|2E}J?ilI(RpY#Hv3YHca)=bh>t$wTT0b76JM|I%|wGDSZ2uBMOpS>ZhoPg zxTDQ<*n)DAR#(oR;GF%AS2o386mC`zSZi?o_jSANp0C}#IJN|L8DX6Tw)` z-dv%vI$zyu*F=97(RulhnaIbkxMpne2g-KJ$sQxdZTaWV)YQmN{>|pIR@9YmG}%jn zo1cD^9w#Ve-n*y!u;wTQb>Tg;xVznZZ0Bk!l`oxR57iLbYRE;8Oe=R#W~p5ud;!Nj7` zz;>{Wv|k}fgFVeDEXB(tvtOJ@Nhj;Clu8G*60eT$dS2~_??lQmIitpX@x3F2xC$ zBTEdQp^6CnmotOKndBaUQr60u^c*THpT&mb5x=5}9*i}F;y#ZH*<>-Vn@0)4eyU-N z%GD}gOojVk$%Cb&0RPmwI3`SdJu&!R$l3s?J+{PigbiCU!R z(a;rh*bg)48s-={-RnJ6?lD*rHCkUihkr*!rIR{xsi?Pw32b`cOW`^33{`|3*u$C+da=W8KkuH9}u6wb|pl!p;6}d^PTrt@qpM2 zBV{kH6ww`1roq!Yssuo^@1j~aI}r?dpCIObmm75#2@n6tsJ`)`c8|u$j*XJX(Rz|7 zi`(jB>ciBva--JS?n{np|+Ptn@Q<`55Z#-wi^F-<&Z3O+x9E=Yp#QnrlHs47dctHP9CSB zQtp6O=w$*z#Ls9ZS@((bJ>K|b7FOG~a?)LKj(K4wq)8`<(m=-I4?kafcd|HJ?(E*$ z8SR8YMi|)r8>x&4QIy3o8@&OvC^(2jV?cYK^PkXQcjJ3Vam?;>n~t}Mn*|FCFvqtu z+RH~)JL3o_?;qUJ^jrNqJ{r8S#%iv}$v=j{?7My`2nYnhPC~~Isoz*4i^PiDEp~gX zyAWLf{-LHIYVV8);k_HZFY~I*lBkpP$Ug9x?aq@9Ga{E_AY>gj5q0#8$_)msRPUo= z!rFraivcV_BB(xy?jvJzjXa~y=9*>rK##Bp!wUv63GpeR!k!_@I9qOFAQ?zeO{ie$ zI|w;4MV9(?7?mW-QFPfR5}AQ;r~y5+_)GQ6ID zV4Kl?H2o*TH;Bxsa=FrBKGbDsy$(WJ)|<%W9~_L`MNSFjF78pa4Wl3#FF zNw+G_SsyTGHKwR|7Vz|PGTUzLo&Sm5CyN@ZZh%Y)4{>FuO?Qp7dac%RD&Dc&1zb{! zOo}l8IXo*hDAaj+Wv<2ryr&c~PQcS02m|CuDO{r4?Y93LmNG?AnzLDXXC(Wr;~QFs z^S6fet@Ze4fw1(;N@>3@4|`7se*P7wZ!uzPOjL6U&X2dVB18wSK3tdzOjy_?Uo~0# zuhu~myw{BNDo4quMZoO~kpbPW=hHyW9l!2RN73aok46gjo3V5=4YrfgN@zerX1t!{ zsrP<#A`=NF238$)PvnUv5$m}c(MPz>TZNuU&dxM=sgu-^bdveeI~IH)`wVs3p|9ij zAZakw`RYSZBqjm!KlSXCO_%d?b?ze^(0R>P^@zGVKUgIPwFTLs zEuo+~PJXoOqs4jfM<{5Vb>C^fv%yAv$!+7%h2MOngRakYcZ9A8;lL_&to5u~{d5yq zA_dToGwJ6G%r+Z`#_?(a^mj+X1FWr1fe89`7@(VKhwNAqFrc-0J7B;IA`g}QtOV-% zR}k{ZCTI*IuFa(#0d+l?5=_G)ywd-JH9y-qMeT4PPl@4*;ri4DYnN;J_bf1;mG8|L m!pQr$UYq|(i~j#t>!sE`l{Pg{o3xr`^3|M% zFG37c5)u?tUa5&-imwUb179dA@)l4KnEegA=iGD8=X378=iFz;sgS@`D>khFfk3N* zgN~dAftCSZbou|57|LaqX~|gJfSiPYK-Gw^7cW~b;brls0}q4n+%3}}&{sQ7oj4PK z%zW6@*4$8yHJMD+Zwtv)Xst$VHkULapUix8<*m^_2QfGy0F zOixaX2@A4doo!8W+3dU0+;$d&-_wqJol}ziXkMqCo*e(wLa%(CgUNq7BNpM%FHtYk zjmAa&!h*2Bk5*H`X{KUcWt8Onp;F4Vnz_YA0|j5YFt0Nim%J8v-Jh0h1qzT!XPRlm zg@t)`Q$4$}4qui(__?<*D+TlFFP&CHC*wZSYu=V1W@p4w$#f+un^;jar&jiI*`vb# z?)DbVoO(>y&!kjQNpDd(>5UEV1$~_?CbLr|1Su7TfOpFgV))RUlYWbiYovZS@KZh+rYUyY^L;_BX)O-+oA z4hgvI4{r*d*WgMN^4TUTQ8G0l7L7I#%gc+i`un;FSR}ujR~+p_+on3=i<}WqEIl5nN8=@F0KDVCd^^dyhpnQLCpW$AtqtwMrqC zOm((3m*zi(d)7LDKvq|Rk3h~O^-G6z6uXvXACzdkcarVT&;DPrZT(Nrt^@>}irMIw4f z*)ZG?urXTSiDVa(Zid6y=!9W((lB{;dYv&I&nYI!%zN0bCs}Z#co6ATer|&k(RqmC zETrs#>PtKW^otFDW4cW#2n~sNzQk5J9xTqp3kG~dDP^cy-6Cl`>*ms)RAizKjbMrY4 z+{#DPGS=G=qr+p{D4yvjs5;&~9)BC8Kz!d_`4DoQwmW{luj4f&^%e5Iu_C?wAhbwR zdjcTkWrZaF@+~{lb}=gR3o-d&YNvNNaPF*zKq!~(b`s5HyOxt(;%v^Y7YU*yVMd9h z@cD6zWT_@8x^t)0RoqI|$#A>aH2WOpS7HsGpnh&~j<#1oE7w^H_Yuh8w9o@`S$L!; z?dR`;^dvK>%Jpn)P--s0jolEX#xgXTq#f&=i#Eo&9efp1zaxc&YXFbNadsP~6H@4& zDL~~ZAUsEk!V=={(B$rD26)=RXg?&8jAfjLn5tcbzCR%Et5|b^efspW#5vRhC#rky zinYFZD`DGxwf&-DT9}=cOIT=IiyaM(}7NhfS>acYofR7;|M|v!=2RwR~<0HwM)jzx}05eZK@!bm*c?Fs>zlSdsbNs$UvL77-WE3mA0!V=u z*CBk*+yUmY7~3ff2IVfmdrBm8x76)≷>txokgq8;OD6;o$5LzCV8C5a#LwE5T5v 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 KHci)lfA&A*jI3z@ literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-mp3.png b/system/images/media/thumb-mp3.png new file mode 100644 index 0000000000000000000000000000000000000000..f72ec75f223a17d44c11d5a1999f04310df631af GIT binary patch literal 2801 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-mpa.png b/system/images/media/thumb-mpa.png new file mode 100644 index 0000000000000000000000000000000000000000..ff85a9df59937478438e98149d33a737fe2f34bc GIT binary patch literal 2734 zcmcIl_ct2~6i#W;DXmRQ&}vmty3|Ouv_h-XsMe@Qi)X8fQTn{L#7?uyc0uj{tg0f4HsgWK-=MD?DR53Ozh$_I|q0RS=k`*-Ya-Y!Kht@GGC z{=*7%>*PXM>u?ikUMLj4@4_V`$8q$PsP>Va-QAM0nV4UrcS@1FLLq;9C!>$}ZD7*2 z4B1Xu3~CYtHmpN9TrF8^aM;aM%_emTm!q2Ir0aCl*PYkP;eu?g$OPc3s6 zIlQXzIoj%mU*o{0V0(6Dy?TP$J;UguGV@2K@bne88qC*z@)(`{V{p>54m&iz9Nj)z zPozSr>b5$hgUN}&BBltIo#}ia?lT7 zQvg7cXKi`iE}Y1kTzDgU^Ehht(siu_^{jV8$36m$)kDwkw~P$bfZ05-yq}(Xai2*m z#7ed*sg*}rn#%vIK-qoa#kF~%7S4&dhDm3ty1hi0JU=WZ((wNXw5>}*2lCLjfo$Xc zBMpj=j`_}gae7+=?uqi8I{5wsB*D;0hIHEAlQF7GrasAdfUBL??D{be5`C<07InEBNQPlz|*vi-o3ezifP>J zDprhWYnQ*-SD6EOEuBG?iRV&j)}Wd5%XyfVwUk#AmpMhg#Z^raD=E!KY&rl#GP(R7+Id@P&GE2aF zC4-oVuvhPvZY32bf*M`HiKXl6@cN0OrrG=%;1 z0qRJZQA6Q-kqg~&d6U;cqt6jmH|#4CzA9A6_O?%{v`SH5t#SsdSB?3y3m~|hjnK8Z z_?F(TGBm%m!Ox5mZypA#DmEOhZ@cWwi%T-El@y+;_d=SO9E^u`-iwy9Or6hcctW~c z9t?!Mx#|HfMurIgc4ePSmuc1&e=X$+Khvm7b|BBUDH%yoPy4%?UA#34EMG(v*Q~>L zK0d09txX&*)}9GYjV6vqXNec#zgx2JI@;PHM~$=-udS-XpMCP*pRU+EAs1=}S6M#= z&hd?`;fHO4ADD7KOUJf=Y;NUc$EfK|QE=u@PTw7g|6TqcM={n>Ip{?fT{ql#SlD|N zw4%Ygi^h|o-@%7IyMc;4{N+pr0}PYrq;0M+0#xiiT|oDP_a0*&$+FwMQg&8CPhR5c zzK%EGNXektC{+3vcIdn6uSAe=8$R+T6xGmYUA1}>&2myHdqC`ZR5 zL_-aXXRlmxHq;I}j?VfmXJfZ9fjcejWHKCY4pw+i(nv#V9+w>1E;A~)a+4O!N~XR1 zXiT>Ng*;toCwXWoAs-#zxAwS>Tr%qFi%?k(tzc3}@#b+1H9l?y-h+zmGFMg3lg?Gc z$!oe4L|EekdFt?Bo2UbSc`GxPLO@VILKHaCCOZVN%Pir6fDu19UO+#C{#q|Jh{cys zA=)*`xL8`*ZFa>zA{FY*764&nh*6AhlTTpWTW#mJGELylWRY;4e$)Kr`$oyJ=9CP& z(^=fddkq8(cFfm$2W2~^SYL!ElC3%<_&|u2{1h-@ZwM&O-C9lTi+gKr0OTUPw46)<5FI*aCEZRC`I|QXa%^Ttk9-Y{{X1j-6O|n&}O? zo$fQ9-cv%9!KkS2)yQ(R5@x?T0sG6yA!_caZ$*GH%>v|GXIksHVss>LzS`Lss@>K^ zX5<$un@L%|iQm|=6d$HA&Y!p7WYD-~%kPz8+yQD|QG!IZDay;NiD}&ODL+V@Aa8P% zdKvytQwV~HKOXo|Co3F?&Y6HZH6FG^XnDr;(;WO|@N5x*xMU6XFQ^Lava$WQCRAB9 zsytmX_T3eU*GEq(!JM-TGGD9;3r7})Y!0Zy@gtRQKj?GsKlhw(Zdf$YbH<%1wyD`H xAfAh|^9)|@^2{3fQ)%#j{+|HAAHst6J-)he8u{7|H~#z7);I53mfv{x=fCPzeKr69 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-mpe.png b/system/images/media/thumb-mpe.png new file mode 100644 index 0000000000000000000000000000000000000000..a66180e6db9c5256cd58582ff80137b2d4f47cbb GIT binary patch literal 1971 zcmbtU`B#$%7EQ-|>;a9qpa`E8L8*#TQ5Zq{IjBJ^wv0~4t>_sPsY4MI%BIu=K@>1z z!4?pd#v-C9ZXp;zL_)&8M9998#q4`R7LulaL*F^?y!-As_q|`OZ@fv~M@8Z8T8o!qjiRKD?)& zc5&We8)ROki<$@Mc{aP*U=}-_&X0n7Gs31}b_q-JoFjd0oReB?`cY1~+Q4FppU}lE zd|Br(>smkU5{aJUaM`$T%O?lLZbgP ztLULHi-uUm6vkyTqrhZQkZ9Rb&FClfu){GgRQAo!TP14BB)>+kon(kxRC>BnPgCeV z+UG4o<@*6fzQv}SRlIXL9eT5nD}BR~JhR#jQ~Y0fGJ?q>*P3`DRsYA?2CATLieD>M zk?EpGle}uVc1oe6QU$eqIZ>sj$+Y8Ui_&aWjdE^I&pyza_%lM>APX(iPH?4orG8p( z7Fq4a8R73V(L=R?NfY8MHmy+6t1)uMxm6ki`;&Tz{pkgTd4==o)g=F}%D@mQ`x)Za z5q6nGJt&ZO2^2j#lVFTfG0ZNt+FXKmgIO{yd@#<%PVnxGux|(z?+tSj#|>uml`)P8$@ zEb?vcMC;M>V)&W@ruW98=H6Sh8P2Pk)r)XZqqnia8eaR$rv^@GG&j;}z)qb=&_MKm_LN4aZ(KQNheu%lHXs z1w8NfQwb7b73PDMl85$pFH03iNGlKcA%o*Kp)#>@%YmT4Vo*zsLHoN&$-A=hvNoeX zOf@dUtwY)Ob8d8L@59TA{BfJfB;t}GxAmylz1Sx3rjFCz5by*{_XgH`Ajby3e;(U? z7nFx`GTSmn+|`FD9ih7D(7cu2FZvB-pKjavtwsFbbr6G?`Cb0K3f_tEx)RBx0TTuS@Jeza8lKfNh zrwjMBK#9$yV{q5*R;3hN(7(JeXVk1~D+VvV zZ;t>H+{^xO4}Oy4);D{{<_KSd%81?uVLL%Souv{g&~2K?>qvAUVt|9hZ9?UmG^nhc z-7;fvE_R~j)}tTtpf?rir{=H-Fr*66(VSXE2Cn zRe76VWk4t*mfX`wiV z71vb;z9p3gsSC{Qo@~H*FCM#@+KJaN!(D@$ z*p#Prn?f?um*LQ02DcyY4=3D$n)Xy2`ln~sE{s78#6+OhLZm2%qZ2fLiQ4-44%9Pn z2|&NSDy804f9qM_I#6)ih01cy02T;4h(0mw75TUpEZxh!X8czynYh*%_kHDK&#do4 z5n6CUWR5?dX2@4=5ZlNw2XX z!l1_H?HOywn9Qac`+OsES?anUsk=SCl0=3WfXWF6hwx1hh{#gZvdc zyEsgxINrdSS-gQAq>%fn@vrFvuTxX>JO8fb!C;T5+-}7V&BB8}SN-Lk340Rb+jgHq F{|WUL69xbP literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-mpg.png b/system/images/media/thumb-mpg.png new file mode 100644 index 0000000000000000000000000000000000000000..19ba9a019a85db732e4658f1513c3b8a02995b72 GIT binary patch literal 2811 zcmcIl`#01H9~Lq;D=J;7Ce?1Kv`Sj7%VY_Q28kjeTDOoS$s0R`CW_I;y|Ga)5xM0u z?&C6Ulg51pBll*^Xbk3ipKZ_kH|%rHb3W&J&Uv1nKIc4-ZO@x4Z`id#Mn*>2(!$hU zMrIx5Unf4b8JW^VCB$R>cfBse-rPi{n5pqqMn=}s*4n|$yjsFp zLb)RJ^)Qsu4?XM#yA}|sR2s(w3pw!EH*{bTefkNssFq+-X#pF~9E1Qwiegd-m4b*= zD8VdiBnT!Q6{6>BB~JAc43nl0K%Mi5RgFXfORqElYq0ckJrLLm++%=C05&7W_-okp zX281zsQm&@FJs>TtaKa}ftVP=8mEz2G3M0_5NjpTJz!)P_>l|S(1A|MgfixlOZC9QD&|iI{OAC04MR|=L!HE>0SIaZ zmO;#;3HYN%^6WG8iHE!$fv+|KFIZ4m2UtDn!5h2)r7E-gDr^HEb2a?lM5$D%LWCOf91; z5ca4S{IZOay1{Erz}yN}G6ws!0DpCX6WN3cqAQP18uv zS7iJfn$!n1Od~hwKsW;&TS9}{fX{psMy1a`L&an8XCC^$PB5Sq_Sz$!UDEF+_6YiWAc;VxS+z8axsuF)Y{C%YvtcxoRdT0Z4(lDw>1SlEWP z!$Pw-HRpWK_0IV;TDO~{Vv3jdojXeji@S;5+!F2y_|;3;U`*H}++*_nXa7Sep6fgk zPrbi7)4ZLhuxAupwJJEcZO;^os=&{=?HtI{ZMtkX-ai~4QMIk3zAz<1L+e=_>kB<{ zSI}krVHQ3jIw(sA%subReCDw8BD3;hj$08q)Hv$5lczY<>E*{F7FA3W#DWll!X}H1 zuBX?DZ68uxy36Z_lThX%zFZoQlV?y(5Efb;eo~g`sW5MT*&MvmEthm0zU?bi_rco_ z1xh&dq8I54h1IFo!tuUFuGHjpIL+DJSxSYSrSGR6={Vv}^yPI&Ps%0rryJ@GIjHls zSXMVSnnq5KdVcAXm)E4F9e49K*bZws7v9)7bMVt1zL%zK(iUsZnbW$|iw2`!!FVTw zh%qZ`{PIpo?yit27qcvr2)QJQ#3r6PNFj?zQWs1oY`;;oPhTQLeN9 z4i!WF2{vKoYRIF@iJL9LB#75T7|uUikUGDI`l<24QcuThd-E- zTaH1-&o$;pio^f*R|={v_Ad-uI8|O&q&%bViB2}jZpwZ~`xs{z>2@jH(3<~iPRd(n zb@F1+JL&!=LaU=-%wQx~r^?ybt>k2hXR2cuV}h{DdcXHhFMPJOy7_JG&H<-Jmgm-( z_{x^ZAq9I+;LLd?J&K6OI3DED@$7bh;D|w@`;2Pka&#q$aV`uTaF^TEKTw-czE;BS z%97|iB|2IW+3E~Kr&Y!4;EQ5hHX5HXJW(8_5+Kr#P8-!BI!KpfeGU1DKiS{U=kW + + diff --git a/system/src/Grav/Common/Errors/SimplePageHandler.php b/system/src/Grav/Common/Errors/SimplePageHandler.php new file mode 100644 index 0000000..e954717 --- /dev/null +++ b/system/src/Grav/Common/Errors/SimplePageHandler.php @@ -0,0 +1,122 @@ +searchPaths[] = __DIR__ . '/Resources'; + } + + /** + * @return int + */ + public function handle() + { + $inspector = $this->getInspector(); + + $helper = new TemplateHelper(); + $templateFile = $this->getResource('layout.html.php'); + $cssFile = $this->getResource('error.css'); + + $code = $inspector->getException()->getCode(); + if (($code >= 400) && ($code < 600)) { + $this->getRun()->sendHttpCode($code); + } + $message = $inspector->getException()->getMessage(); + + if ($inspector->getException() instanceof ErrorException) { + $code = Misc::translateErrorCode($code); + } + + $vars = array( + 'stylesheet' => file_get_contents($cssFile), + 'code' => $code, + 'message' => htmlspecialchars(strip_tags(rawurldecode($message)), ENT_QUOTES, 'UTF-8'), + ); + + $helper->setVariables($vars); + $helper->render($templateFile); + + return Handler::QUIT; + } + + /** + * @param string $resource + * @return string + * @throws RuntimeException + */ + protected function getResource($resource) + { + // If the resource was found before, we can speed things up + // by caching its absolute, resolved path: + if (isset($this->resourceCache[$resource])) { + return $this->resourceCache[$resource]; + } + + // Search through available search paths, until we find the + // resource we're after: + foreach ($this->searchPaths as $path) { + $fullPath = "{$path}/{$resource}"; + + if (is_file($fullPath)) { + // Cache the result: + $this->resourceCache[$resource] = $fullPath; + return $fullPath; + } + } + + // If we got this far, nothing was found. + throw new RuntimeException( + "Could not find resource '{$resource}' in any resource paths (searched: " . implode(', ', $this->searchPaths). ')' + ); + } + + /** + * @param string $path + * @return void + */ + public function addResourcePath($path) + { + if (!is_dir($path)) { + throw new InvalidArgumentException( + "'{$path}' is not a valid directory" + ); + } + + array_unshift($this->searchPaths, $path); + } + + /** + * @return array + */ + public function getResourcePaths() + { + return $this->searchPaths; + } +} diff --git a/system/src/Grav/Common/Errors/SystemFacade.php b/system/src/Grav/Common/Errors/SystemFacade.php new file mode 100644 index 0000000..4a775cd --- /dev/null +++ b/system/src/Grav/Common/Errors/SystemFacade.php @@ -0,0 +1,67 @@ +whoopsShutdownHandler = $function; + register_shutdown_function([$this, 'handleShutdown']); + } + + /** + * Special case to deal with Fatal errors and the like. + * + * @return void + */ + public function handleShutdown() + { + $error = $this->getLastError(); + + // Ignore core warnings and errors. + if ($error && !($error['type'] & (E_CORE_WARNING | E_CORE_ERROR))) { + $handler = $this->whoopsShutdownHandler; + $handler(); + } + } + + + /** + * @param int $httpCode + * + * @return int + */ + public function setHttpResponseCode($httpCode) + { + if (!headers_sent()) { + // Ensure that no 'location' header is present as otherwise this + // will override the HTTP code being set here, and mask the + // expected error page. + header_remove('location'); + + // Work around PHP bug #8218 (8.0.17 & 8.1.4). + header_remove('Content-Encoding'); + } + + return http_response_code($httpCode); + } +} diff --git a/system/src/Grav/Common/File/CompiledFile.php b/system/src/Grav/Common/File/CompiledFile.php new file mode 100644 index 0000000..152bc0e --- /dev/null +++ b/system/src/Grav/Common/File/CompiledFile.php @@ -0,0 +1,195 @@ +filename; + // If nothing has been loaded, attempt to get pre-compiled version of the file first. + if ($var === null && $this->raw === null && $this->content === null) { + $key = md5($filename); + $file = PhpFile::instance(CACHE_DIR . "compiled/files/{$key}{$this->extension}.php"); + + $modified = $this->modified(); + if (!$modified) { + try { + return $this->decode($this->raw()); + } catch (Throwable $e) { + // If the compiled file is broken, we can safely ignore the error and continue. + } + } + + $class = get_class($this); + + $size = filesize($filename); + $cache = $file->exists() ? $file->content() : null; + + // Load real file if cache isn't up to date (or is invalid). + if (!isset($cache['@class']) + || $cache['@class'] !== $class + || $cache['modified'] !== $modified + || ($cache['size'] ?? null) !== $size + || $cache['filename'] !== $filename + ) { + // Attempt to lock the file for writing. + try { + $locked = $file->lock(false); + } catch (Exception $e) { + $locked = false; + + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + $debugger->addMessage(sprintf('%s(): Cannot obtain a lock for compiling cache file for %s: %s', __METHOD__, $this->filename, $e->getMessage()), 'warning'); + } + + // Decode RAW file into compiled array. + $data = (array)$this->decode($this->raw()); + $cache = [ + '@class' => $class, + 'filename' => $filename, + 'modified' => $modified, + 'size' => $size, + 'data' => $data + ]; + + // If compiled file wasn't already locked by another process, save it. + if ($locked) { + $file->save($cache); + $file->unlock(); + + // Compile cached file into bytecode cache + if (function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) { + $lockName = $file->filename(); + + // Silence error if function exists, but is restricted. + @opcache_invalidate($lockName, true); + @opcache_compile_file($lockName); + } + } + } + $file->free(); + + $this->content = $cache['data']; + } + } catch (Exception $e) { + throw new RuntimeException(sprintf('Failed to read %s: %s', Utils::basename($filename), $e->getMessage()), 500, $e); + } + + return parent::content($var); + } + + /** + * Save file. + * + * @param mixed $data Optional data to be saved, usually array. + * @return void + * @throws RuntimeException + */ + public function save($data = null) + { + // Make sure that the cache file is always up to date! + $key = md5($this->filename); + $file = PhpFile::instance(CACHE_DIR . "compiled/files/{$key}{$this->extension}.php"); + try { + $locked = $file->lock(); + } catch (Exception $e) { + $locked = false; + + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + $debugger->addMessage(sprintf('%s(): Cannot obtain a lock for compiling cache file for %s: %s', __METHOD__, $this->filename, $e->getMessage()), 'warning'); + } + + parent::save($data); + + if ($locked) { + $modified = $this->modified(); + $filename = $this->filename; + $class = get_class($this); + $size = filesize($filename); + + // windows doesn't play nicely with this as it can't read when locked + if (!Utils::isWindows()) { + // Reload data from the filesystem. This ensures that we always cache the correct data (see issue #2282). + $this->raw = $this->content = null; + $data = (array)$this->decode($this->raw()); + } + + // Decode data into compiled array. + $cache = [ + '@class' => $class, + 'filename' => $filename, + 'modified' => $modified, + 'size' => $size, + 'data' => $data + ]; + + $file->save($cache); + $file->unlock(); + + // Compile cached file into bytecode cache + if (function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) { + $lockName = $file->filename(); + // Silence error if function exists, but is restricted. + @opcache_invalidate($lockName, true); + @opcache_compile_file($lockName); + } + } + } + + /** + * Serialize file. + * + * @return array + */ + public function __sleep() + { + return [ + 'filename', + 'extension', + 'raw', + 'content', + 'settings' + ]; + } + + /** + * Unserialize file. + */ + public function __wakeup() + { + if (!isset(static::$instances[$this->filename])) { + static::$instances[$this->filename] = $this; + } + } +} diff --git a/system/src/Grav/Common/File/CompiledJsonFile.php b/system/src/Grav/Common/File/CompiledJsonFile.php new file mode 100644 index 0000000..22b9edb --- /dev/null +++ b/system/src/Grav/Common/File/CompiledJsonFile.php @@ -0,0 +1,33 @@ + ['.DS_Store'], + 'exclude_paths' => [] + ]; + + /** @var string */ + protected $archive_file; + + /** + * @param string $compression + * @return ZipArchiver + */ + public static function create($compression) + { + if ($compression === 'zip') { + return new ZipArchiver(); + } + + return new ZipArchiver(); + } + + /** + * @param string $archive_file + * @return $this + */ + public function setArchive($archive_file) + { + $this->archive_file = $archive_file; + + return $this; + } + + /** + * @param array $options + * @return $this + */ + public function setOptions($options) + { + // Set infinite PHP execution time if possible. + if (Utils::functionExists('set_time_limit')) { + @set_time_limit(0); + } + + $this->options = $options + $this->options; + + return $this; + } + + /** + * @param string $folder + * @param callable|null $status + * @return $this + */ + abstract public function compress($folder, callable $status = null); + + /** + * @param string $destination + * @param callable|null $status + * @return $this + */ + abstract public function extract($destination, callable $status = null); + + /** + * @param array $folders + * @param callable|null $status + * @return $this + */ + abstract public function addEmptyFolders($folders, callable $status = null); + + /** + * @param string $rootPath + * @return RecursiveIteratorIterator + */ + protected function getArchiveFiles($rootPath) + { + $exclude_paths = $this->options['exclude_paths']; + $exclude_files = $this->options['exclude_files']; + $dirItr = new RecursiveDirectoryIterator($rootPath, RecursiveDirectoryIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS | FilesystemIterator::UNIX_PATHS); + $filterItr = new RecursiveDirectoryFilterIterator($dirItr, $rootPath, $exclude_paths, $exclude_files); + $files = new RecursiveIteratorIterator($filterItr, RecursiveIteratorIterator::SELF_FIRST); + + return $files; + } +} diff --git a/system/src/Grav/Common/Filesystem/Folder.php b/system/src/Grav/Common/Filesystem/Folder.php new file mode 100644 index 0000000..019342f --- /dev/null +++ b/system/src/Grav/Common/Filesystem/Folder.php @@ -0,0 +1,548 @@ +isStream($path)) { + $directory = $locator->getRecursiveIterator($path, $flags); + } else { + $directory = new RecursiveDirectoryIterator($path, $flags); + } + $filter = new RecursiveFolderFilterIterator($directory); + $iterator = new RecursiveIteratorIterator($filter, RecursiveIteratorIterator::SELF_FIRST); + + foreach ($iterator as $dir) { + $dir_modified = $dir->getMTime(); + if ($dir_modified > $last_modified) { + $last_modified = $dir_modified; + } + } + } + + return $last_modified; + } + + /** + * Recursively find the last modified time under given path by file. + * + * @param array $paths + * @param string $extensions which files to search for specifically + * @return int + */ + public static function lastModifiedFile(array $paths, $extensions = 'md|yaml'): int + { + $last_modified = 0; + + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + $flags = RecursiveDirectoryIterator::SKIP_DOTS; + + foreach($paths as $path) { + if (!file_exists($path)) { + return 0; + } + if ($locator->isStream($path)) { + $directory = $locator->getRecursiveIterator($path, $flags); + } else { + $directory = new RecursiveDirectoryIterator($path, $flags); + } + $recursive = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST); + $iterator = new RegexIterator($recursive, '/^.+\.'.$extensions.'$/i'); + + /** @var RecursiveDirectoryIterator $file */ + foreach ($iterator as $file) { + try { + $file_modified = $file->getMTime(); + if ($file_modified > $last_modified) { + $last_modified = $file_modified; + } + } catch (Exception $e) { + Grav::instance()['log']->error('Could not process file: ' . $e->getMessage()); + } + } + } + + return $last_modified; + } + + /** + * Recursively md5 hash all files in a path + * + * @param array $paths + * @return string + */ + public static function hashAllFiles(array $paths): string + { + $files = []; + + foreach ($paths as $path) { + if (file_exists($path)) { + $flags = RecursiveDirectoryIterator::SKIP_DOTS; + + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + if ($locator->isStream($path)) { + $directory = $locator->getRecursiveIterator($path, $flags); + } else { + $directory = new RecursiveDirectoryIterator($path, $flags); + } + + $iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST); + + foreach ($iterator as $file) { + $files[] = $file->getPathname() . '?'. $file->getMTime(); + } + } + } + + return md5(serialize($files)); + } + + /** + * Get relative path between target and base path. If path isn't relative, return full path. + * + * @param string $path + * @param string $base + * @return string + */ + public static function getRelativePath($path, $base = GRAV_ROOT) + { + if ($base) { + $base = preg_replace('![\\\/]+!', '/', $base); + $path = preg_replace('![\\\/]+!', '/', $path); + if (strpos($path, $base) === 0) { + $path = ltrim(substr($path, strlen($base)), '/'); + } + } + + return $path; + } + + /** + * Get relative path between target and base path. If path isn't relative, return full path. + * + * @param string $path + * @param string $base + * @return string + */ + public static function getRelativePathDotDot($path, $base) + { + // Normalize paths. + $base = preg_replace('![\\\/]+!', '/', $base); + $path = preg_replace('![\\\/]+!', '/', $path); + + if ($path === $base) { + return ''; + } + + $baseParts = explode('/', ltrim($base, '/')); + $pathParts = explode('/', ltrim($path, '/')); + + array_pop($baseParts); + $lastPart = array_pop($pathParts); + foreach ($baseParts as $i => $directory) { + if (isset($pathParts[$i]) && $pathParts[$i] === $directory) { + unset($baseParts[$i], $pathParts[$i]); + } else { + break; + } + } + $pathParts[] = $lastPart; + $path = str_repeat('../', count($baseParts)) . implode('/', $pathParts); + + return '' === $path + || strpos($path, '/') === 0 + || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos) + ? "./$path" : $path; + } + + /** + * Shift first directory out of the path. + * + * @param string $path + * @return string|null + */ + public static function shift(&$path) + { + $parts = explode('/', trim($path, '/'), 2); + $result = array_shift($parts); + $path = array_shift($parts); + + return $result ?: null; + } + + /** + * Return recursive list of all files and directories under given path. + * + * @param string $path + * @param array $params + * @return array + * @throws RuntimeException + */ + public static function all($path, array $params = []) + { + if (!$path) { + throw new RuntimeException("Path doesn't exist."); + } + if (!file_exists($path)) { + return []; + } + + $compare = isset($params['compare']) ? 'get' . $params['compare'] : null; + $pattern = $params['pattern'] ?? null; + $filters = $params['filters'] ?? null; + $recursive = $params['recursive'] ?? true; + $levels = $params['levels'] ?? -1; + $key = isset($params['key']) ? 'get' . $params['key'] : null; + $value = 'get' . ($params['value'] ?? ($recursive ? 'SubPathname' : 'Filename')); + $folders = $params['folders'] ?? true; + $files = $params['files'] ?? true; + + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + if ($recursive) { + $flags = RecursiveDirectoryIterator::SKIP_DOTS + FilesystemIterator::UNIX_PATHS + + FilesystemIterator::CURRENT_AS_SELF + FilesystemIterator::FOLLOW_SYMLINKS; + if ($locator->isStream($path)) { + $directory = $locator->getRecursiveIterator($path, $flags); + } else { + $directory = new RecursiveDirectoryIterator($path, $flags); + } + $iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST); + $iterator->setMaxDepth(max($levels, -1)); + } else { + if ($locator->isStream($path)) { + $iterator = $locator->getIterator($path); + } else { + $iterator = new FilesystemIterator($path); + } + } + + $results = []; + + /** @var RecursiveDirectoryIterator $file */ + foreach ($iterator as $file) { + // Ignore hidden files. + if (strpos($file->getFilename(), '.') === 0 && $file->isFile()) { + continue; + } + if (!$folders && $file->isDir()) { + continue; + } + if (!$files && $file->isFile()) { + continue; + } + if ($compare && $pattern && !preg_match($pattern, $file->{$compare}())) { + continue; + } + $fileKey = $key ? $file->{$key}() : null; + $filePath = $file->{$value}(); + if ($filters) { + if (isset($filters['key'])) { + $pre = !empty($filters['pre-key']) ? $filters['pre-key'] : ''; + $fileKey = $pre . preg_replace($filters['key'], '', $fileKey); + } + if (isset($filters['value'])) { + $filter = $filters['value']; + if (is_callable($filter)) { + $filePath = $filter($file); + } else { + $filePath = preg_replace($filter, '', $filePath); + } + } + } + + if ($fileKey !== null) { + $results[$fileKey] = $filePath; + } else { + $results[] = $filePath; + } + } + + return $results; + } + + /** + * Recursively copy directory in filesystem. + * + * @param string $source + * @param string $target + * @param string|null $ignore Ignore files matching pattern (regular expression). + * @return void + * @throws RuntimeException + */ + public static function copy($source, $target, $ignore = null) + { + $source = rtrim($source, '\\/'); + $target = rtrim($target, '\\/'); + + if (!is_dir($source)) { + throw new RuntimeException('Cannot copy non-existing folder.'); + } + + // Make sure that path to the target exists before copying. + self::create($target); + + $success = true; + + // Go through all sub-directories and copy everything. + $files = self::all($source); + foreach ($files as $file) { + if ($ignore && preg_match($ignore, $file)) { + continue; + } + $src = $source .'/'. $file; + $dst = $target .'/'. $file; + + if (is_dir($src)) { + // Create current directory (if it doesn't exist). + if (!is_dir($dst)) { + $success &= @mkdir($dst, 0777, true); + } + } else { + // Or copy current file. + $success &= @copy($src, $dst); + } + } + + if (!$success) { + $error = error_get_last(); + throw new RuntimeException($error['message'] ?? 'Unknown error'); + } + + // Make sure that the change will be detected when caching. + @touch(dirname($target)); + } + + /** + * Move directory in filesystem. + * + * @param string $source + * @param string $target + * @return void + * @throws RuntimeException + */ + public static function move($source, $target) + { + if (!file_exists($source) || !is_dir($source)) { + // Rename fails if source folder does not exist. + throw new RuntimeException('Cannot move non-existing folder.'); + } + + // Don't do anything if the source is the same as the new target + if ($source === $target) { + return; + } + + if (strpos($target, $source . '/') === 0) { + throw new RuntimeException('Cannot move folder to itself'); + } + + if (file_exists($target)) { + // Rename fails if target folder exists. + throw new RuntimeException('Cannot move files to existing folder/file.'); + } + + // Make sure that path to the target exists before moving. + self::create(dirname($target)); + + // Silence warnings (chmod failed etc). + @rename($source, $target); + + // Rename function can fail while still succeeding, so let's check if the folder exists. + if (is_dir($source)) { + // Rename doesn't support moving folders across filesystems. Use copy instead. + self::copy($source, $target); + self::delete($source); + } + + // Make sure that the change will be detected when caching. + @touch(dirname($source)); + @touch(dirname($target)); + @touch($target); + } + + /** + * Recursively delete directory from filesystem. + * + * @param string $target + * @param bool $include_target + * @return bool + * @throws RuntimeException + */ + public static function delete($target, $include_target = true) + { + if (!is_dir($target)) { + return false; + } + + $success = self::doDelete($target, $include_target); + + if (!$success) { + $error = error_get_last(); + + throw new RuntimeException($error['message'] ?? 'Unknown error'); + } + + // Make sure that the change will be detected when caching. + if ($include_target) { + @touch(dirname($target)); + } else { + @touch($target); + } + + return $success; + } + + /** + * @param string $folder + * @return void + * @throws RuntimeException + */ + public static function mkdir($folder) + { + self::create($folder); + } + + /** + * @param string $folder + * @return void + * @throws RuntimeException + */ + public static function create($folder) + { + // Silence error for open_basedir; should fail in mkdir instead. + if (@is_dir($folder)) { + return; + } + + $success = @mkdir($folder, 0777, true); + + if (!$success) { + // Take yet another look, make sure that the folder doesn't exist. + clearstatcache(true, $folder); + if (!@is_dir($folder)) { + throw new RuntimeException(sprintf('Unable to create directory: %s', $folder)); + } + } + } + + /** + * Recursive copy of one directory to another + * + * @param string $src + * @param string $dest + * @return bool + * @throws RuntimeException + */ + public static function rcopy($src, $dest) + { + + // If the src is not a directory do a simple file copy + if (!is_dir($src)) { + copy($src, $dest); + return true; + } + + // If the destination directory does not exist create it + if (!is_dir($dest)) { + static::create($dest); + } + + // 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()) { + static::rcopy($f->getRealPath(), "{$dest}/{$f}"); + } + } + } + return true; + } + + /** + * Does a directory contain children + * + * @param string $directory + * @return int|false + */ + public static function countChildren($directory) + { + if (!is_dir($directory)) { + return false; + } + $directories = glob($directory . '/*', GLOB_ONLYDIR); + + return $directories ? count($directories) : false; + } + + /** + * @param string $folder + * @param bool $include_target + * @return bool + * @internal + */ + protected static function doDelete($folder, $include_target = true) + { + // Special case for symbolic links. + if ($include_target && is_link($folder)) { + return @unlink($folder); + } + + // Go through all items in filesystem and recursively remove everything. + $files = scandir($folder, SCANDIR_SORT_NONE); + $files = $files ? array_diff($files, ['.', '..']) : []; + foreach ($files as $file) { + $path = "{$folder}/{$file}"; + is_dir($path) ? self::doDelete($path) : @unlink($path); + } + + return $include_target ? @rmdir($folder) : true; + } +} diff --git a/system/src/Grav/Common/Filesystem/RecursiveDirectoryFilterIterator.php b/system/src/Grav/Common/Filesystem/RecursiveDirectoryFilterIterator.php new file mode 100644 index 0000000..ad8e0cd --- /dev/null +++ b/system/src/Grav/Common/Filesystem/RecursiveDirectoryFilterIterator.php @@ -0,0 +1,119 @@ +current(); + $filename = $file->getFilename(); + $relative_filename = str_replace($this::$root . '/', '', $file->getPathname()); + + if ($file->isDir()) { + // Check if the directory path is in the ignore list + if (in_array($relative_filename, $this::$ignore_folders, true)) { + return false; + } + // Check if any parent directory is in the ignore list + foreach ($this::$ignore_folders as $ignore_folder) { + $ignore_folder = trim($ignore_folder, '/'); + if (strpos($relative_filename, $ignore_folder . '/') === 0 || $relative_filename === $ignore_folder) { + return false; + } + } + if (!$this->matchesPattern($filename, $this::$ignore_files)) { + return true; + } + } elseif ($file->isFile() && !$this->matchesPattern($filename, $this::$ignore_files)) { + return true; + } + return false; + } + + /** + * Check if filename matches any pattern in the list + * + * @param string $filename + * @param array $patterns + * @return bool + */ + protected function matchesPattern($filename, $patterns) + { + foreach ($patterns as $pattern) { + // Check for exact match + if ($filename === $pattern) { + return true; + } + // Check for extension patterns like .pdf + if (strpos($pattern, '.') === 0 && substr($filename, -strlen($pattern)) === $pattern) { + return true; + } + // Check for wildcard patterns + if (strpos($pattern, '*') !== false) { + $regex = '/^' . str_replace('\\*', '.*', preg_quote($pattern, '/')) . '$/'; + if (preg_match($regex, $filename)) { + return true; + } + } + } + return false; + } + + /** + * @return RecursiveDirectoryFilterIterator|RecursiveFilterIterator + */ + public function getChildren() :RecursiveFilterIterator + { + /** @var RecursiveDirectoryFilterIterator $iterator */ + $iterator = $this->getInnerIterator(); + + return new self($iterator->getChildren(), $this::$root, $this::$ignore_folders, $this::$ignore_files); + } +} diff --git a/system/src/Grav/Common/Filesystem/RecursiveFolderFilterIterator.php b/system/src/Grav/Common/Filesystem/RecursiveFolderFilterIterator.php new file mode 100644 index 0000000..b43cdc1 --- /dev/null +++ b/system/src/Grav/Common/Filesystem/RecursiveFolderFilterIterator.php @@ -0,0 +1,55 @@ +get('system.pages.ignore_folders'); + } + + $this::$ignore_folders = $ignore_folders; + } + + /** + * Check whether the current element of the iterator is acceptable + * + * @return bool true if the current element is acceptable, otherwise false. + */ + public function accept() :bool + { + /** @var SplFileInfo $current */ + $current = $this->current(); + + return $current->isDir() && !in_array($current->getFilename(), $this::$ignore_folders, true); + } +} diff --git a/system/src/Grav/Common/Filesystem/ZipArchiver.php b/system/src/Grav/Common/Filesystem/ZipArchiver.php new file mode 100644 index 0000000..770e84a --- /dev/null +++ b/system/src/Grav/Common/Filesystem/ZipArchiver.php @@ -0,0 +1,166 @@ +open($this->archive_file); + + if ($archive === true) { + Folder::create($destination); + + if (!$zip->extractTo($destination)) { + throw new RuntimeException('ZipArchiver: ZIP failed to extract ' . $this->archive_file . ' to ' . $destination); + } + + $zip->close(); + + return $this; + } + + throw new RuntimeException('ZipArchiver: Failed to open ' . $this->archive_file); + } + + /** + * @param string $source + * @param callable|null $status + * @return $this + */ + public function compress($source, callable $status = null) + { + if (!extension_loaded('zip')) { + throw new InvalidArgumentException('ZipArchiver: Zip PHP module not installed...'); + } + + // Get real path for our folder + $rootPath = realpath($source); + if (!$rootPath) { + throw new InvalidArgumentException('ZipArchiver: ' . $source . ' cannot be found...'); + } + + $zip = new ZipArchive(); + $result = $zip->open($this->archive_file, ZipArchive::CREATE); + if ($result !== true) { + $error = 'unknown error'; + if ($result === ZipArchive::ER_NOENT) { + $error = 'file does not exist'; + } elseif ($result === ZipArchive::ER_EXISTS) { + $error = 'file already exists'; + } elseif ($result === ZipArchive::ER_OPEN) { + $error = 'cannot open file'; + } elseif ($result === ZipArchive::ER_READ) { + $error = 'read error'; + } elseif ($result === ZipArchive::ER_SEEK) { + $error = 'seek error'; + } + throw new InvalidArgumentException('ZipArchiver: ' . $this->archive_file . ' cannot be created: ' . $error); + } + + $files = $this->getArchiveFiles($rootPath); + + $status && $status([ + 'type' => 'count', + 'steps' => iterator_count($files), + ]); + + foreach ($files as $file) { + $filePath = $file->getPathname(); + $relativePath = ltrim(substr($filePath, strlen($rootPath)), '/'); + + if ($file->isDir()) { + $zip->addEmptyDir($relativePath); + } else { + $zip->addFile($filePath, $relativePath); + } + + $status && $status([ + 'type' => 'progress', + ]); + } + + $status && $status([ + 'type' => 'message', + 'message' => 'Compressing...' + ]); + + $zip->close(); + + return $this; + } + + /** + * @param array $folders + * @param callable|null $status + * @return $this + */ + public function addEmptyFolders($folders, callable $status = null) + { + if (!extension_loaded('zip')) { + throw new InvalidArgumentException('ZipArchiver: Zip PHP module not installed...'); + } + + $zip = new ZipArchive(); + $result = $zip->open($this->archive_file); + if ($result !== true) { + $error = 'unknown error'; + if ($result === ZipArchive::ER_NOENT) { + $error = 'file does not exist'; + } elseif ($result === ZipArchive::ER_EXISTS) { + $error = 'file already exists'; + } elseif ($result === ZipArchive::ER_OPEN) { + $error = 'cannot open file'; + } elseif ($result === ZipArchive::ER_READ) { + $error = 'read error'; + } elseif ($result === ZipArchive::ER_SEEK) { + $error = 'seek error'; + } + throw new InvalidArgumentException('ZipArchiver: ' . $this->archive_file . ' cannot be opened: ' . $error); + } + + $status && $status([ + 'type' => 'message', + 'message' => 'Adding empty folders...' + ]); + + foreach ($folders as $folder) { + if ($zip->addEmptyDir($folder) === false) { + $status && $status([ + 'type' => 'message', + 'message' => 'Warning: Could not add empty directory: ' . $folder + ]); + } + $status && $status([ + 'type' => 'progress', + ]); + } + + $zip->close(); + + return $this; + } +} diff --git a/system/src/Grav/Common/Flex/FlexCollection.php b/system/src/Grav/Common/Flex/FlexCollection.php new file mode 100644 index 0000000..a8b9122 --- /dev/null +++ b/system/src/Grav/Common/Flex/FlexCollection.php @@ -0,0 +1,28 @@ + + */ +abstract class FlexCollection extends \Grav\Framework\Flex\FlexCollection +{ + use FlexGravTrait; + use FlexCollectionTrait; +} diff --git a/system/src/Grav/Common/Flex/FlexIndex.php b/system/src/Grav/Common/Flex/FlexIndex.php new file mode 100644 index 0000000..045934a --- /dev/null +++ b/system/src/Grav/Common/Flex/FlexIndex.php @@ -0,0 +1,29 @@ + + */ +abstract class FlexIndex extends \Grav\Framework\Flex\FlexIndex +{ + use FlexGravTrait; + use FlexIndexTrait; +} diff --git a/system/src/Grav/Common/Flex/FlexObject.php b/system/src/Grav/Common/Flex/FlexObject.php new file mode 100644 index 0000000..ae87b0c --- /dev/null +++ b/system/src/Grav/Common/Flex/FlexObject.php @@ -0,0 +1,74 @@ +getNestedProperty($name, null, $separator); + + // Handle media order field. + if (null === $value && $name === 'media_order') { + return implode(',', $this->getMediaOrder()); + } + + // Handle media fields. + $settings = $this->getFieldSettings($name); + if (($settings['media_field'] ?? false) === true) { + return $this->parseFileProperty($value, $settings); + } + + return $value ?? $default; + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::prepareStorage() + */ + public function prepareStorage(): array + { + // Remove extra content from media fields. + $fields = $this->getMediaFields(); + foreach ($fields as $field) { + $data = $this->getNestedProperty($field); + if (is_array($data)) { + foreach ($data as $name => &$image) { + unset($image['image_url'], $image['thumb_url']); + } + unset($image); + $this->setNestedProperty($field, $data); + } + } + + return parent::prepareStorage(); + } +} diff --git a/system/src/Grav/Common/Flex/Traits/FlexCollectionTrait.php b/system/src/Grav/Common/Flex/Traits/FlexCollectionTrait.php new file mode 100644 index 0000000..76049d9 --- /dev/null +++ b/system/src/Grav/Common/Flex/Traits/FlexCollectionTrait.php @@ -0,0 +1,51 @@ + 'flex', + 'directory' => $this->getFlexDirectory(), + 'collection' => $this + ]); + } + if (strpos($name, 'onFlexCollection') !== 0 && strpos($name, 'on') === 0) { + $name = 'onFlexCollection' . substr($name, 2); + } + + $container = $this->getContainer(); + if ($event instanceof Event) { + $container->fireEvent($name, $event); + } else { + $container->dispatchEvent($event); + } + + return $this; + } +} diff --git a/system/src/Grav/Common/Flex/Traits/FlexCommonTrait.php b/system/src/Grav/Common/Flex/Traits/FlexCommonTrait.php new file mode 100644 index 0000000..1ab9e0c --- /dev/null +++ b/system/src/Grav/Common/Flex/Traits/FlexCommonTrait.php @@ -0,0 +1,54 @@ +getContainer(); + + /** @var Twig $twig */ + $twig = $container['twig']; + + try { + return $twig->twig()->resolveTemplate($this->getTemplatePaths($layout)); + } catch (LoaderError $e) { + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + $debugger->addException($e); + + return $twig->twig()->resolveTemplate(['flex/404.html.twig']); + } + } + + abstract protected function getTemplatePaths(string $layout): array; + abstract protected function getContainer(): Grav; +} diff --git a/system/src/Grav/Common/Flex/Traits/FlexGravTrait.php b/system/src/Grav/Common/Flex/Traits/FlexGravTrait.php new file mode 100644 index 0000000..05f376c --- /dev/null +++ b/system/src/Grav/Common/Flex/Traits/FlexGravTrait.php @@ -0,0 +1,74 @@ +getContainer(); + + /** @var Flex $flex */ + $flex = $container['flex']; + + return $flex; + } + + /** + * @return UserInterface|null + */ + protected function getActiveUser(): ?UserInterface + { + $container = $this->getContainer(); + + /** @var UserInterface|null $user */ + $user = $container['user'] ?? null; + + return $user; + } + + /** + * @return bool + */ + protected function isAdminSite(): bool + { + $container = $this->getContainer(); + + return isset($container['admin']); + } + + /** + * @return string + */ + protected function getAuthorizeScope(): string + { + return $this->isAdminSite() ? 'admin' : 'site'; + } +} diff --git a/system/src/Grav/Common/Flex/Traits/FlexIndexTrait.php b/system/src/Grav/Common/Flex/Traits/FlexIndexTrait.php new file mode 100644 index 0000000..a362971 --- /dev/null +++ b/system/src/Grav/Common/Flex/Traits/FlexIndexTrait.php @@ -0,0 +1,20 @@ + 'onFlexObjectRender', + 'onBeforeSave' => 'onFlexObjectBeforeSave', + 'onAfterSave' => 'onFlexObjectAfterSave', + 'onBeforeDelete' => 'onFlexObjectBeforeDelete', + 'onAfterDelete' => 'onFlexObjectAfterDelete' + ]; + + if (null === $event) { + $event = new Event([ + 'type' => 'flex', + 'directory' => $this->getFlexDirectory(), + 'object' => $this + ]); + } + + if (isset($events['name'])) { + $name = $events['name']; + } elseif (strpos($name, 'onFlexObject') !== 0 && strpos($name, 'on') === 0) { + $name = 'onFlexObject' . substr($name, 2); + } + + $container = $this->getContainer(); + if ($event instanceof Event) { + $container->fireEvent($name, $event); + } else { + $container->dispatchEvent($event); + } + + return $this; + } +} diff --git a/system/src/Grav/Common/Flex/Types/Generic/GenericCollection.php b/system/src/Grav/Common/Flex/Types/Generic/GenericCollection.php new file mode 100644 index 0000000..ee10f6d --- /dev/null +++ b/system/src/Grav/Common/Flex/Types/Generic/GenericCollection.php @@ -0,0 +1,24 @@ + + */ +class GenericCollection extends FlexCollection +{ +} diff --git a/system/src/Grav/Common/Flex/Types/Generic/GenericIndex.php b/system/src/Grav/Common/Flex/Types/Generic/GenericIndex.php new file mode 100644 index 0000000..c774b0f --- /dev/null +++ b/system/src/Grav/Common/Flex/Types/Generic/GenericIndex.php @@ -0,0 +1,24 @@ + + */ +class GenericIndex extends FlexIndex +{ +} diff --git a/system/src/Grav/Common/Flex/Types/Generic/GenericObject.php b/system/src/Grav/Common/Flex/Types/Generic/GenericObject.php new file mode 100644 index 0000000..8a74326 --- /dev/null +++ b/system/src/Grav/Common/Flex/Types/Generic/GenericObject.php @@ -0,0 +1,22 @@ + + * @implements PageCollectionInterface + * + * Incompatibilities with Grav\Common\Page\Collection: + * $page = $collection->key() will not work at all + * $clone = clone $collection does not clone objects inside the collection, does it matter? + * $string = (string)$collection returns collection id instead of comma separated list + * $collection->add() incompatible method signature + * $collection->remove() incompatible method signature + * $collection->filter() incompatible method signature (takes closure instead of callable) + * $collection->prev() does not rewind the internal pointer + * AND most methods are immutable; they do not update the current collection, but return updated one + * + * @method PageIndex getIndex() + */ +class PageCollection extends FlexPageCollection implements PageCollectionInterface +{ + use FlexGravTrait; + use FlexCollectionTrait; + + /** @var array|null */ + protected $_params; + + /** + * @return array + */ + public static function getCachedMethods(): array + { + return [ + // Collection specific methods + 'getRoot' => false, + 'getParams' => false, + 'setParams' => false, + 'params' => false, + 'addPage' => false, + 'merge' => false, + 'intersect' => false, + 'prev' => false, + 'nth' => false, + 'random' => false, + 'append' => false, + 'batch' => false, + 'order' => false, + + // Collection filtering + 'dateRange' => true, + 'visible' => true, + 'nonVisible' => true, + 'pages' => true, + 'modules' => true, + 'modular' => true, + 'nonModular' => true, + 'published' => true, + 'nonPublished' => true, + 'routable' => true, + 'nonRoutable' => true, + 'ofType' => true, + 'ofOneOfTheseTypes' => true, + 'ofOneOfTheseAccessLevels' => true, + 'withOrdered' => true, + 'withModules' => true, + 'withPages' => true, + 'withTranslation' => true, + 'filterBy' => true, + + 'toExtendedArray' => false, + 'getLevelListing' => false, + ] + parent::getCachedMethods(); + } + + /** + * @return PageInterface + */ + public function getRoot() + { + return $this->getIndex()->getRoot(); + } + + /** + * Get the collection params + * + * @return array + */ + public function getParams(): array + { + return $this->_params ?? []; + } + + /** + * Set parameters to the Collection + * + * @param array $params + * @return $this + */ + public function setParams(array $params) + { + $this->_params = $this->_params ? array_merge($this->_params, $params) : $params; + + return $this; + } + + /** + * Get the collection params + * + * @return array + */ + public function params(): array + { + return $this->getParams(); + } + + /** + * Add a single page to a collection + * + * @param PageInterface $page + * @return $this + */ + public function addPage(PageInterface $page) + { + if (!$page instanceof PageObject) { + throw new InvalidArgumentException('$page is not a flex page.'); + } + + // FIXME: support other keys. + $this->set($page->getKey(), $page); + + return $this; + } + + /** + * + * Merge another collection with the current collection + * + * @param PageCollectionInterface $collection + * @return static + * @phpstan-return static + */ + public function merge(PageCollectionInterface $collection) + { + throw new RuntimeException(__METHOD__ . '(): Not Implemented'); + } + + /** + * Intersect another collection with the current collection + * + * @param PageCollectionInterface $collection + * @return static + * @phpstan-return static + */ + public function intersect(PageCollectionInterface $collection) + { + throw new RuntimeException(__METHOD__ . '(): Not Implemented'); + } + + /** + * Set current page. + */ + public function setCurrent(string $path): void + { + throw new RuntimeException(__METHOD__ . '(): Not Implemented'); + } + + /** + * Return previous item. + * + * @return PageInterface|false + * @phpstan-return T|false + */ + public function prev() + { + // FIXME: this method does not rewind the internal pointer! + $key = (string)$this->key(); + $prev = $this->prevSibling($key); + + return $prev !== $this->current() ? $prev : false; + } + + /** + * Return nth item. + * @param int $key + * @return PageInterface|bool + * @phpstan-return T|false + */ + public function nth($key) + { + return $this->slice($key, 1)[0] ?? false; + } + + /** + * Pick one or more random entries. + * + * @param int $num Specifies how many entries should be picked. + * @return static + * @phpstan-return static + */ + public function random($num = 1) + { + return $this->createFrom($this->shuffle()->slice(0, $num)); + } + + /** + * Append new elements to the list. + * + * @param array $items Items to be appended. Existing keys will be overridden with the new values. + * @return static + * @phpstan-return static + */ + public function append($items) + { + throw new RuntimeException(__METHOD__ . '(): Not Implemented'); + } + + /** + * Split collection into array of smaller collections. + * + * @param int $size + * @return static[] + * @phpstan-return static[] + */ + public function batch($size): array + { + $chunks = $this->chunk($size); + + $list = []; + foreach ($chunks as $chunk) { + $list[] = $this->createFrom($chunk); + } + + return $list; + } + + /** + * Reorder collection. + * + * @param string $by + * @param string $dir + * @param array|null $manual + * @param int|null $sort_flags + * @return static + * @phpstan-return static + */ + public function order($by, $dir = 'asc', $manual = null, $sort_flags = null) + { + if (!$this->count()) { + return $this; + } + + if ($by === 'random') { + return $this->shuffle(); + } + + $keys = $this->buildSort($by, $dir, $manual, $sort_flags); + + return $this->createFrom(array_replace(array_flip($keys), $this->toArray()) ?? []); + } + + /** + * @param string $order_by + * @param string $order_dir + * @param array|null $manual + * @param int|null $sort_flags + * @return array + */ + protected function buildSort($order_by = 'default', $order_dir = 'asc', $manual = null, $sort_flags = null): array + { + // do this header query work only once + $header_query = null; + $header_default = null; + if (strpos($order_by, 'header.') === 0) { + $query = explode('|', str_replace('header.', '', $order_by), 2); + $header_query = array_shift($query) ?? ''; + $header_default = array_shift($query); + } + + $list = []; + foreach ($this as $key => $child) { + switch ($order_by) { + case 'title': + $list[$key] = $child->title(); + break; + case 'date': + $list[$key] = $child->date(); + $sort_flags = SORT_REGULAR; + break; + case 'modified': + $list[$key] = $child->modified(); + $sort_flags = SORT_REGULAR; + break; + case 'publish_date': + $list[$key] = $child->publishDate(); + $sort_flags = SORT_REGULAR; + break; + case 'unpublish_date': + $list[$key] = $child->unpublishDate(); + $sort_flags = SORT_REGULAR; + break; + case 'slug': + $list[$key] = $child->slug(); + break; + case 'basename': + $list[$key] = Utils::basename($key); + break; + case 'folder': + $list[$key] = $child->folder(); + break; + case 'manual': + case 'default': + default: + if (is_string($header_query)) { + /** @var Header $child_header */ + $child_header = $child->header(); + $header_value = $child_header->get($header_query); + if (is_array($header_value)) { + $list[$key] = implode(',', $header_value); + } elseif ($header_value) { + $list[$key] = $header_value; + } else { + $list[$key] = $header_default ?: $key; + } + $sort_flags = $sort_flags ?: SORT_REGULAR; + break; + } + $list[$key] = $key; + $sort_flags = $sort_flags ?: SORT_REGULAR; + } + } + + if (null === $sort_flags) { + $sort_flags = SORT_NATURAL | SORT_FLAG_CASE; + } + + // else just sort the list according to specified key + if (extension_loaded('intl') && Grav::instance()['config']->get('system.intl_enabled')) { + $locale = setlocale(LC_COLLATE, '0'); //`setlocale` with a '0' param returns the current locale set + $col = Collator::create($locale); + if ($col) { + $col->setAttribute(Collator::NUMERIC_COLLATION, Collator::ON); + if (($sort_flags & SORT_NATURAL) === SORT_NATURAL) { + $list = preg_replace_callback('~([0-9]+)\.~', static function ($number) { + return sprintf('%032d.', $number[0]); + }, $list); + if (!is_array($list)) { + throw new RuntimeException('Internal Error'); + } + + $list_vals = array_values($list); + if (is_numeric(array_shift($list_vals))) { + $sort_flags = Collator::SORT_REGULAR; + } else { + $sort_flags = Collator::SORT_STRING; + } + } + + $col->asort($list, $sort_flags); + } else { + asort($list, $sort_flags); + } + } else { + asort($list, $sort_flags); + } + + // Move manually ordered items into the beginning of the list. Order of the unlisted items does not change. + if (is_array($manual) && !empty($manual)) { + $i = count($manual); + $new_list = []; + foreach ($list as $key => $dummy) { + $child = $this[$key] ?? null; + $order = $child ? array_search($child->slug, $manual, true) : false; + if ($order === false) { + $order = $i++; + } + $new_list[$key] = (int)$order; + } + + $list = $new_list; + + // Apply manual ordering to the list. + asort($list, SORT_NUMERIC); + } + + if ($order_dir !== 'asc') { + $list = array_reverse($list); + } + + return array_keys($list); + } + + /** + * Mimicks Pages class. + * + * @return $this + * @deprecated 1.7 Not needed anymore in Flex Pages (does nothing). + */ + public function all() + { + return $this; + } + + /** + * Returns the items between a set of date ranges of either the page date field (default) or + * an arbitrary datetime page field where start date and end date are optional + * Dates must be passed in as text that strtotime() can process + * http://php.net/manual/en/function.strtotime.php + * + * @param string|null $startDate + * @param string|null $endDate + * @param string|null $field + * @return static + * @phpstan-return static + * @throws Exception + */ + public function dateRange($startDate = null, $endDate = null, $field = null) + { + $start = $startDate ? Utils::date2timestamp($startDate) : null; + $end = $endDate ? Utils::date2timestamp($endDate) : null; + + $entries = []; + foreach ($this as $key => $object) { + if (!$object) { + continue; + } + + $date = $field ? strtotime($object->getNestedProperty($field)) : $object->date(); + + if ((!$start || $date >= $start) && (!$end || $date <= $end)) { + $entries[$key] = $object; + } + } + + return $this->createFrom($entries); + } + + /** + * Creates new collection with only visible pages + * + * @return static The collection with only visible pages + * @phpstan-return static + */ + public function visible() + { + $entries = []; + foreach ($this as $key => $object) { + if ($object && $object->visible()) { + $entries[$key] = $object; + } + } + + return $this->createFrom($entries); + } + + /** + * Creates new collection with only non-visible pages + * + * @return static The collection with only non-visible pages + * @phpstan-return static + */ + public function nonVisible() + { + $entries = []; + foreach ($this as $key => $object) { + if ($object && !$object->visible()) { + $entries[$key] = $object; + } + } + + return $this->createFrom($entries); + } + + /** + * Creates new collection with only pages + * + * @return static The collection with only pages + * @phpstan-return static + */ + public function pages() + { + $entries = []; + /** + * @var int|string $key + * @var PageInterface|null $object + */ + foreach ($this as $key => $object) { + if ($object && !$object->isModule()) { + $entries[$key] = $object; + } + } + + return $this->createFrom($entries); + } + + /** + * Creates new collection with only modules + * + * @return static The collection with only modules + * @phpstan-return static + */ + public function modules() + { + $entries = []; + /** + * @var int|string $key + * @var PageInterface|null $object + */ + foreach ($this as $key => $object) { + if ($object && $object->isModule()) { + $entries[$key] = $object; + } + } + + return $this->createFrom($entries); + } + + /** + * Alias of modules() + * + * @return static + * @phpstan-return static + */ + public function modular() + { + return $this->modules(); + } + + /** + * Alias of pages() + * + * @return static + * @phpstan-return static + */ + public function nonModular() + { + return $this->pages(); + } + + /** + * Creates new collection with only published pages + * + * @return static The collection with only published pages + * @phpstan-return static + */ + public function published() + { + $entries = []; + foreach ($this as $key => $object) { + if ($object && $object->published()) { + $entries[$key] = $object; + } + } + + return $this->createFrom($entries); + } + + /** + * Creates new collection with only non-published pages + * + * @return static The collection with only non-published pages + * @phpstan-return static + */ + public function nonPublished() + { + $entries = []; + foreach ($this as $key => $object) { + if ($object && !$object->published()) { + $entries[$key] = $object; + } + } + + return $this->createFrom($entries); + } + + /** + * Creates new collection with only routable pages + * + * @return static The collection with only routable pages + * @phpstan-return static + */ + public function routable() + { + $entries = []; + foreach ($this as $key => $object) { + if ($object && $object->routable()) { + $entries[$key] = $object; + } + } + + return $this->createFrom($entries); + } + + /** + * Creates new collection with only non-routable pages + * + * @return static The collection with only non-routable pages + * @phpstan-return static + */ + public function nonRoutable() + { + $entries = []; + foreach ($this as $key => $object) { + if ($object && !$object->routable()) { + $entries[$key] = $object; + } + } + + return $this->createFrom($entries); + } + + /** + * Creates new collection with only pages of the specified type + * + * @param string $type + * @return static The collection + * @phpstan-return static + */ + public function ofType($type) + { + $entries = []; + foreach ($this as $key => $object) { + if ($object && $object->template() === $type) { + $entries[$key] = $object; + } + } + + return $this->createFrom($entries); + } + + /** + * Creates new collection with only pages of one of the specified types + * + * @param string[] $types + * @return static The collection + * @phpstan-return static + */ + public function ofOneOfTheseTypes($types) + { + $entries = []; + foreach ($this as $key => $object) { + if ($object && in_array($object->template(), $types, true)) { + $entries[$key] = $object; + } + } + + return $this->createFrom($entries); + } + + /** + * Creates new collection with only pages of one of the specified access levels + * + * @param array $accessLevels + * @return static The collection + * @phpstan-return static + */ + public function ofOneOfTheseAccessLevels($accessLevels) + { + $entries = []; + foreach ($this as $key => $object) { + if ($object && isset($object->header()->access)) { + if (is_array($object->header()->access)) { + //Multiple values for access + $valid = false; + + foreach ($object->header()->access as $index => $accessLevel) { + if (is_array($accessLevel)) { + foreach ($accessLevel as $innerIndex => $innerAccessLevel) { + if (in_array($innerAccessLevel, $accessLevels)) { + $valid = true; + } + } + } else { + if (in_array($index, $accessLevels)) { + $valid = true; + } + } + } + if ($valid) { + $entries[$key] = $object; + } + } else { + //Single value for access + if (in_array($object->header()->access, $accessLevels)) { + $entries[$key] = $object; + } + } + } + } + + return $this->createFrom($entries); + } + + /** + * @param bool $bool + * @return static + * @phpstan-return static + */ + public function withOrdered(bool $bool = true) + { + $list = array_keys(array_filter($this->call('isOrdered', [$bool]))); + + return $this->select($list); + } + + /** + * @param bool $bool + * @return static + * @phpstan-return static + */ + public function withModules(bool $bool = true) + { + $list = array_keys(array_filter($this->call('isModule', [$bool]))); + + return $this->select($list); + } + + /** + * @param bool $bool + * @return static + * @phpstan-return static + */ + public function withPages(bool $bool = true) + { + $list = array_keys(array_filter($this->call('isPage', [$bool]))); + + return $this->select($list); + } + + /** + * @param bool $bool + * @param string|null $languageCode + * @param bool|null $fallback + * @return static + * @phpstan-return static + */ + public function withTranslation(bool $bool = true, string $languageCode = null, bool $fallback = null) + { + $list = array_keys(array_filter($this->call('hasTranslation', [$languageCode, $fallback]))); + + return $bool ? $this->select($list) : $this->unselect($list); + } + + /** + * @param string|null $languageCode + * @param bool|null $fallback + * @return PageIndex + */ + public function withTranslated(string $languageCode = null, bool $fallback = null) + { + return $this->getIndex()->withTranslated($languageCode, $fallback); + } + + /** + * Filter pages by given filters. + * + * - search: string + * - page_type: string|string[] + * - modular: bool + * - visible: bool + * - routable: bool + * - published: bool + * - page: bool + * - translated: bool + * + * @param array $filters + * @param bool $recursive + * @return static + * @phpstan-return static + */ + public function filterBy(array $filters, bool $recursive = false) + { + $list = array_keys(array_filter($this->call('filterBy', [$filters, $recursive]))); + + return $this->select($list); + } + + /** + * Get the extended version of this Collection with each page keyed by route + * + * @return array + * @throws Exception + */ + public function toExtendedArray(): array + { + $entries = []; + foreach ($this as $key => $object) { + if ($object) { + $entries[$object->route()] = $object->toArray(); + } + } + + return $entries; + } + + /** + * @param array $options + * @return array + */ + public function getLevelListing(array $options): array + { + /** @var PageIndex $index */ + $index = $this->getIndex(); + + return method_exists($index, 'getLevelListing') ? $index->getLevelListing($options) : []; + } +} diff --git a/system/src/Grav/Common/Flex/Types/Pages/PageIndex.php b/system/src/Grav/Common/Flex/Types/Pages/PageIndex.php new file mode 100644 index 0000000..ac8e899 --- /dev/null +++ b/system/src/Grav/Common/Flex/Types/Pages/PageIndex.php @@ -0,0 +1,1198 @@ + + * @implements PageCollectionInterface + * + * @method PageIndex withModules(bool $bool = true) + * @method PageIndex withPages(bool $bool = true) + * @method PageIndex withTranslation(bool $bool = true, string $languageCode = null, bool $fallback = null) + */ +class PageIndex extends FlexPageIndex implements PageCollectionInterface +{ + use FlexGravTrait; + use FlexIndexTrait; + + public const VERSION = parent::VERSION . '.5'; + public const ORDER_LIST_REGEX = '/(\/\d+)\.[^\/]+/u'; + public const PAGE_ROUTE_REGEX = '/\/\d+\./u'; + + /** @var PageObject|array */ + protected $_root; + /** @var array|null */ + protected $_params; + + /** + * @param array $entries + * @param FlexDirectory|null $directory + */ + public function __construct(array $entries = [], FlexDirectory $directory = null) + { + // Remove root if it's taken. + if (isset($entries[''])) { + $this->_root = $entries['']; + unset($entries['']); + } + + parent::__construct($entries, $directory); + } + + /** + * @param FlexStorageInterface $storage + * @return array + */ + public static function loadEntriesFromStorage(FlexStorageInterface $storage): array + { + // Load saved index. + $index = static::loadIndex($storage); + + $version = $index['version'] ?? 0; + $force = static::VERSION !== $version; + + // TODO: Following check flex index to be out of sync after some saves, disabled until better solution is found. + //$timestamp = $index['timestamp'] ?? 0; + //if (!$force && $timestamp && $timestamp > time() - 1) { + // return $index['index']; + //} + + // Load up to date index. + $entries = parent::loadEntriesFromStorage($storage); + + return static::updateIndexFile($storage, $index['index'], $entries, ['include_missing' => true, 'force_update' => $force]); + } + + /** + * @param string $key + * @return PageObject|null + * @phpstan-return T|null + */ + public function get($key) + { + if (mb_strpos($key, '|') !== false) { + [$key, $params] = explode('|', $key, 2); + } + + $element = parent::get($key); + if (null === $element) { + return null; + } + + if (isset($params)) { + $element = $element->getTranslation(ltrim($params, '.')); + } + + \assert(null === $element || $element instanceof PageObject); + + return $element; + } + + /** + * @return PageInterface + */ + public function getRoot() + { + $root = $this->_root; + if (is_array($root)) { + $directory = $this->getFlexDirectory(); + $storage = $directory->getStorage(); + + $defaults = [ + 'header' => [ + 'routable' => false, + 'permissions' => [ + 'inherit' => false + ] + ] + ]; + + $row = $storage->readRows(['' => null])[''] ?? null; + if (null !== $row) { + if (isset($row['__ERROR'])) { + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + $message = sprintf('Flex Pages: root page is broken in storage: %s', $row['__ERROR']); + + $debugger->addException(new RuntimeException($message)); + $debugger->addMessage($message, 'error'); + + $row = ['__META' => $root]; + } + + } else { + $row = ['__META' => $root]; + } + + $row = array_merge_recursive($defaults, $row); + + /** @var PageObject $root */ + $root = $this->getFlexDirectory()->createObject($row, '/', false); + $root->name('root.md'); + $root->root(true); + + $this->_root = $root; + } + + return $root; + } + + /** + * @param string|null $languageCode + * @param bool|null $fallback + * @return static + * @phpstan-return static + */ + public function withTranslated(string $languageCode = null, bool $fallback = null) + { + if (null === $languageCode) { + return $this; + } + + $entries = $this->translateEntries($this->getEntries(), $languageCode, $fallback); + $params = ['language' => $languageCode, 'language_fallback' => $fallback] + $this->getParams(); + + return $this->createFrom($entries)->setParams($params); + } + + /** + * @return string|null + */ + public function getLanguage(): ?string + { + return $this->_params['language'] ?? null; + } + + /** + * Get the collection params + * + * @return array + */ + public function getParams(): array + { + return $this->_params ?? []; + } + + /** + * Get the collection param + * + * @param string $name + * @return mixed + */ + public function getParam(string $name) + { + return $this->_params[$name] ?? null; + } + + /** + * Set parameters to the Collection + * + * @param array $params + * @return $this + */ + public function setParams(array $params) + { + $this->_params = $this->_params ? array_merge($this->_params, $params) : $params; + + return $this; + } + + /** + * Set a parameter to the Collection + * + * @param string $name + * @param mixed $value + * @return $this + */ + public function setParam(string $name, $value) + { + $this->_params[$name] = $value; + + return $this; + } + + /** + * Get the collection params + * + * @return array + */ + public function params(): array + { + return $this->getParams(); + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::getCacheKey() + */ + public function getCacheKey(): string + { + return $this->getTypePrefix() . $this->getFlexType() . '.' . sha1(json_encode($this->getKeys()) . $this->getKeyField() . $this->getLanguage()); + } + + /** + * Filter pages by given filters. + * + * - search: string + * - page_type: string|string[] + * - modular: bool + * - visible: bool + * - routable: bool + * - published: bool + * - page: bool + * - translated: bool + * + * @param array $filters + * @param bool $recursive + * @return static + * @phpstan-return static + */ + public function filterBy(array $filters, bool $recursive = false) + { + if (!$filters) { + return $this; + } + + if ($recursive) { + return $this->__call('filterBy', [$filters, true]); + } + + $list = []; + $index = $this; + foreach ($filters as $key => $value) { + switch ($key) { + case 'search': + $index = $index->search((string)$value); + break; + case 'page_type': + if (!is_array($value)) { + $value = is_string($value) && $value !== '' ? explode(',', $value) : []; + } + $index = $index->ofOneOfTheseTypes($value); + break; + case 'routable': + $index = $index->withRoutable((bool)$value); + break; + case 'published': + $index = $index->withPublished((bool)$value); + break; + case 'visible': + $index = $index->withVisible((bool)$value); + break; + case 'module': + $index = $index->withModules((bool)$value); + break; + case 'page': + $index = $index->withPages((bool)$value); + break; + case 'folder': + $index = $index->withPages(!$value); + break; + case 'translated': + $index = $index->withTranslation((bool)$value); + break; + default: + $list[$key] = $value; + } + } + + return $list ? $index->filterByParent($list) : $index; + } + + /** + * @param array $filters + * @return static + * @phpstan-return static + */ + protected function filterByParent(array $filters) + { + /** @var static $index */ + $index = parent::filterBy($filters); + + return $index; + } + + /** + * @param array $options + * @return array + */ + public function getLevelListing(array $options): array + { + // Undocumented B/C + $order = $options['order'] ?? 'asc'; + if ($order === SORT_ASC) { + $options['order'] = 'asc'; + } elseif ($order === SORT_DESC) { + $options['order'] = 'desc'; + } + + $options += [ + 'field' => null, + 'route' => null, + 'leaf_route' => null, + 'sortby' => null, + 'order' => 'asc', + 'lang' => null, + 'filters' => [], + ]; + + $options['filters'] += [ + 'type' => ['root', 'dir'], + ]; + + $key = 'page.idx.lev.' . sha1(json_encode($options, JSON_THROW_ON_ERROR) . $this->getCacheKey()); + $checksum = $this->getCacheChecksum(); + + $cache = $this->getCache('object'); + + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + + $result = null; + try { + $cached = $cache->get($key); + $test = $cached[0] ?? null; + $result = $test === $checksum ? ($cached[1] ?? null) : null; + } catch (\Psr\SimpleCache\InvalidArgumentException $e) { + $debugger->addException($e); + } + + try { + if (null === $result) { + $result = $this->getLevelListingRecurse($options); + $cache->set($key, [$checksum, $result]); + } + } catch (\Psr\SimpleCache\InvalidArgumentException $e) { + $debugger->addException($e); + } + + return $result; + } + + /** + * @param array $entries + * @param string|null $keyField + * @return static + * @phpstan-return static + */ + protected function createFrom(array $entries, string $keyField = null) + { + /** @var static $index */ + $index = parent::createFrom($entries, $keyField); + $index->_root = $this->getRoot(); + + return $index; + } + + /** + * @param array $entries + * @param string $lang + * @param bool|null $fallback + * @return array + */ + protected function translateEntries(array $entries, string $lang, bool $fallback = null): array + { + $languages = $this->getFallbackLanguages($lang, $fallback); + foreach ($entries as $key => &$entry) { + // Find out which version of the page we should load. + $translations = $this->getLanguageTemplates((string)$key); + if (!$translations) { + // No translations found, is this a folder? + continue; + } + + // Find a translation. + $template = null; + foreach ($languages as $code) { + if (isset($translations[$code])) { + $template = $translations[$code]; + break; + } + } + + // We couldn't find a translation, remove entry from the list. + if (!isset($code, $template)) { + unset($entries['key']); + continue; + } + + // Get the main key without template and language. + [$main_key,] = explode('|', $entry['storage_key'] . '|', 2); + + // Update storage key and language. + $entry['storage_key'] = $main_key . '|' . $template . '.' . $code; + $entry['lang'] = $code; + } + unset($entry); + + return $entries; + } + + /** + * @return array + */ + protected function getLanguageTemplates(string $key): array + { + $meta = $this->getMetaData($key); + $template = $meta['template'] ?? 'folder'; + $translations = $meta['markdown'] ?? []; + $list = []; + foreach ($translations as $code => $search) { + if (isset($search[$template])) { + // Use main template if possible. + $list[$code] = $template; + } elseif (!empty($search)) { + // Fall back to first matching template. + $list[$code] = key($search); + } + } + + return $list; + } + + /** + * @param string|null $languageCode + * @param bool|null $fallback + * @return array + */ + protected function getFallbackLanguages(string $languageCode = null, bool $fallback = null): array + { + $fallback = $fallback ?? true; + if (!$fallback && null !== $languageCode) { + return [$languageCode]; + } + + $grav = Grav::instance(); + + /** @var Language $language */ + $language = $grav['language']; + $languageCode = $languageCode ?? ''; + if ($languageCode === '' && $fallback) { + return $language->getFallbackLanguages(null, true); + } + + return $fallback ? $language->getFallbackLanguages($languageCode, true) : [$languageCode]; + } + + /** + * @param array $options + * @return array + */ + protected function getLevelListingRecurse(array $options): array + { + $filters = $options['filters'] ?? []; + $field = $options['field']; + $route = $options['route']; + $leaf_route = $options['leaf_route']; + $sortby = $options['sortby']; + $order = $options['order']; + $language = $options['lang']; + + $status = 'error'; + $response = []; + $extra = null; + + // Handle leaf_route + $leaf = null; + if ($leaf_route && $route !== $leaf_route) { + $nodes = explode('/', $leaf_route); + $sub_route = '/' . implode('/', array_slice($nodes, 1, $options['level']++)); + $options['route'] = $sub_route; + + [$status,,$leaf,$extra] = $this->getLevelListingRecurse($options); + } + + // Handle no route, assume page tree root + if (!$route) { + $page = $this->getRoot(); + } else { + $page = $this->get(trim($route, '/')); + } + $path = $page ? $page->path() : null; + + if ($field) { + // Get forced filters from the field. + $blueprint = $page ? $page->getBlueprint() : $this->getFlexDirectory()->getBlueprint(); + $settings = $blueprint->schema()->getProperty($field); + $filters = array_merge([], $filters, $settings['filters'] ?? []); + } + + // Clean up filter. + $filter_type = (array)($filters['type'] ?? []); + unset($filters['type']); + $filters = array_filter($filters, static function($val) { return $val !== null && $val !== ''; }); + + if ($page) { + $status = 'success'; + $msg = 'PLUGIN_ADMIN.PAGE_ROUTE_FOUND'; + + if ($page->root() && (!$filter_type || in_array('root', $filter_type, true))) { + if ($field) { + $response[] = [ + 'name' => '', + 'value' => '/', + 'item-key' => '', + 'filename' => '.', + 'extension' => '', + 'type' => 'root', + 'modified' => $page->modified(), + 'size' => 0, + 'symlink' => false, + 'has-children' => false + ]; + } else { + $response[] = [ + 'item-key' => '-root-', + 'icon' => 'root', + 'title' => 'Root', // FIXME + 'route' => [ + 'display' => '<root>', // FIXME + 'raw' => '_root', + ], + 'modified' => $page->modified(), + 'extras' => [ + 'template' => $page->template(), + //'lang' => null, + //'translated' => null, + 'langs' => [], + 'published' => false, + 'visible' => false, + 'routable' => false, + 'tags' => ['root', 'non-routable'], + 'actions' => ['edit'], // FIXME + ] + ]; + } + } + + /** @var PageCollection|PageIndex $children */ + $children = $page->children(); + /** @var PageIndex $children */ + $children = $children->getIndex(); + $selectedChildren = $children->filterBy($filters + ['language' => $language], true); + + /** @var Header $header */ + $header = $page->header(); + + if (!$field && $header->get('admin.children_display_order', 'collection') === 'collection' && ($orderby = $header->get('content.order.by'))) { + // Use custom sorting by page header. + $sortby = $orderby; + $order = $header->get('content.order.dir', $order); + $custom = $header->get('content.order.custom'); + } + + if ($sortby) { + // Sort children. + $selectedChildren = $selectedChildren->order($sortby, $order, $custom ?? null); + } + + /** @var UserInterface|null $user */ + $user = Grav::instance()['user'] ?? null; + + /** @var PageObject $child */ + foreach ($selectedChildren as $child) { + $selected = $child->path() === $extra; + $includeChildren = is_array($leaf) && !empty($leaf) && $selected; + if ($field) { + $child_count = count($child->children()); + $payload = [ + 'name' => $child->menu(), + 'value' => $child->rawRoute(), + 'item-key' => Utils::basename($child->rawRoute() ?? ''), + 'filename' => $child->folder(), + 'extension' => $child->extension(), + 'type' => 'dir', + 'modified' => $child->modified(), + 'size' => $child_count, + 'symlink' => false, + 'has-children' => $child_count > 0 + ]; + } else { + $lang = $child->findTranslation($language) ?? 'n/a'; + /** @var PageObject $child */ + $child = $child->getTranslation($language) ?? $child; + + // TODO: all these features are independent from each other, we cannot just have one icon/color to catch all. + // TODO: maybe icon by home/modular/page/folder (or even from blueprints) and color by visibility etc.. + if ($child->home()) { + $icon = 'home'; + } elseif ($child->isModule()) { + $icon = 'modular'; + } elseif ($child->visible()) { + $icon = 'visible'; + } elseif ($child->isPage()) { + $icon = 'page'; + } else { + // TODO: add support + $icon = 'folder'; + } + $tags = [ + $child->published() ? 'published' : 'non-published', + $child->visible() ? 'visible' : 'non-visible', + $child->routable() ? 'routable' : 'non-routable' + ]; + $extras = [ + 'template' => $child->template(), + 'lang' => $lang ?: null, + 'translated' => $lang ? $child->hasTranslation($language, false) : null, + 'langs' => $child->getAllLanguages(true) ?: null, + 'published' => $child->published(), + 'published_date' => $this->jsDate($child->publishDate()), + 'unpublished_date' => $this->jsDate($child->unpublishDate()), + 'visible' => $child->visible(), + 'routable' => $child->routable(), + 'tags' => $tags, + 'actions' => $this->getListingActions($child, $user), + ]; + $extras = array_filter($extras, static function ($v) { + return $v !== null; + }); + + /** @var PageIndex $tmp */ + $tmp = $child->children()->getIndex(); + $child_count = $tmp->count(); + $count = $filters ? $tmp->filterBy($filters, true)->count() : null; + $route = $child->getRoute(); + $route = $route ? ($route->toString(false) ?: '/') : ''; + $payload = [ + 'item-key' => htmlspecialchars(Utils::basename($child->rawRoute() ?? $child->getKey())), + 'icon' => $icon, + 'title' => htmlspecialchars($child->menu()), + 'route' => [ + 'display' => htmlspecialchars($route) ?: null, + 'raw' => htmlspecialchars($child->rawRoute()), + ], + 'modified' => $this->jsDate($child->modified()), + 'child_count' => $child_count ?: null, + 'count' => $count ?? null, + 'filters_hit' => $filters ? ($child->filterBy($filters, false) ?: null) : null, + 'extras' => $extras + ]; + $payload = array_filter($payload, static function ($v) { + return $v !== null; + }); + } + + // Add children if any + if ($includeChildren) { + $payload['children'] = array_values($leaf); + } + + $response[] = $payload; + } + } else { + $msg = 'PLUGIN_ADMIN.PAGE_ROUTE_NOT_FOUND'; + } + + if ($field) { + $temp_array = []; + foreach ($response as $index => $item) { + $temp_array[$item['type']][$index] = $item; + } + + $sorted = Utils::sortArrayByArray($temp_array, $filter_type); + $response = Utils::arrayFlatten($sorted); + } + + return [$status, $msg, $response, $path]; + } + + /** + * @param PageObject $object + * @param UserInterface $user + * @return array + */ + protected function getListingActions(PageObject $object, UserInterface $user): array + { + $actions = []; + if ($object->isAuthorized('read', null, $user)) { + $actions[] = 'preview'; + $actions[] = 'edit'; + } + if ($object->isAuthorized('update', null, $user)) { + $actions[] = 'copy'; + $actions[] = 'move'; + } + if ($object->isAuthorized('delete', null, $user)) { + $actions[] = 'delete'; + } + + return $actions; + } + + /** + * @param FlexStorageInterface $storage + * @return CompiledJsonFile|CompiledYamlFile|null + */ + protected static function getIndexFile(FlexStorageInterface $storage) + { + if (!method_exists($storage, 'isIndexed') || !$storage->isIndexed()) { + return null; + } + + // Load saved index file. + $grav = Grav::instance(); + $locator = $grav['locator']; + + $filename = $locator->findResource('user-data://flex/indexes/pages.json', true, true); + + return CompiledJsonFile::instance($filename); + } + + /** + * @param int|null $timestamp + * @return string|null + */ + private function jsDate(int $timestamp = null): ?string + { + if (!$timestamp) { + return null; + } + + $config = Grav::instance()['config']; + $dateFormat = $config->get('system.pages.dateformat.long'); + + return date($dateFormat, $timestamp) ?: null; + } + + /** + * Add a single page to a collection + * + * @param PageInterface $page + * @return PageCollection + * @phpstan-return C + */ + public function addPage(PageInterface $page) + { + return $this->getCollection()->addPage($page); + } + + /** + * + * Create a copy of this collection + * + * @return static + * @phpstan-return static + */ + public function copy() + { + return clone $this; + } + + /** + * + * Merge another collection with the current collection + * + * @param PageCollectionInterface $collection + * @return PageCollection + * @phpstan-return C + */ + public function merge(PageCollectionInterface $collection) + { + return $this->getCollection()->merge($collection); + } + + + /** + * Intersect another collection with the current collection + * + * @param PageCollectionInterface $collection + * @return PageCollection + * @phpstan-return C + */ + public function intersect(PageCollectionInterface $collection) + { + return $this->getCollection()->intersect($collection); + } + + /** + * Split collection into array of smaller collections. + * + * @param int $size + * @return PageCollection[] + * @phpstan-return C[] + */ + public function batch($size) + { + return $this->getCollection()->batch($size); + } + + /** + * Remove item from the list. + * + * @param string $key + * @return PageObject|null + * @phpstan-return T|null + * @throws InvalidArgumentException + */ + public function remove($key) + { + return $this->getCollection()->remove($key); + } + + /** + * Reorder collection. + * + * @param string $by + * @param string $dir + * @param array $manual + * @param string $sort_flags + * @return static + * @phpstan-return static + */ + public function order($by, $dir = 'asc', $manual = null, $sort_flags = null) + { + /** @var PageCollectionInterface $collection */ + $collection = $this->__call('order', [$by, $dir, $manual, $sort_flags]); + + return $collection; + } + + /** + * Check to see if this item is the first in the collection. + * + * @param string $path + * @return bool True if item is first. + */ + public function isFirst($path): bool + { + /** @var bool $result */ + $result = $this->__call('isFirst', [$path]); + + return $result; + + } + + /** + * Check to see if this item is the last in the collection. + * + * @param string $path + * @return bool True if item is last. + */ + public function isLast($path): bool + { + /** @var bool $result */ + $result = $this->__call('isLast', [$path]); + + return $result; + } + + /** + * Gets the previous sibling based on current position. + * + * @param string $path + * @return PageObject|null The previous item. + * @phpstan-return T|null + */ + public function prevSibling($path) + { + /** @var PageObject|null $result */ + $result = $this->__call('prevSibling', [$path]); + + return $result; + } + + /** + * Gets the next sibling based on current position. + * + * @param string $path + * @return PageObject|null The next item. + * @phpstan-return T|null + */ + public function nextSibling($path) + { + /** @var PageObject|null $result */ + $result = $this->__call('nextSibling', [$path]); + + return $result; + } + + /** + * Returns the adjacent sibling based on a direction. + * + * @param string $path + * @param int $direction either -1 or +1 + * @return PageObject|false The sibling item. + * @phpstan-return T|false + */ + public function adjacentSibling($path, $direction = 1) + { + /** @var PageObject|false $result */ + $result = $this->__call('adjacentSibling', [$path, $direction]); + + return $result; + } + + /** + * Returns the item in the current position. + * + * @param string $path the path the item + * @return int|null The index of the current page, null if not found. + */ + public function currentPosition($path): ?int + { + /** @var int|null $result */ + $result = $this->__call('currentPosition', [$path]); + + return $result; + } + + /** + * Returns the items between a set of date ranges of either the page date field (default) or + * an arbitrary datetime page field where start date and end date are optional + * Dates must be passed in as text that strtotime() can process + * http://php.net/manual/en/function.strtotime.php + * + * @param string|null $startDate + * @param string|null $endDate + * @param string|null $field + * @return static + * @phpstan-return static + * @throws Exception + */ + public function dateRange($startDate = null, $endDate = null, $field = null) + { + $collection = $this->__call('dateRange', [$startDate, $endDate, $field]); + + return $collection; + } + + /** + * Mimicks Pages class. + * + * @return $this + * @deprecated 1.7 Not needed anymore in Flex Pages (does nothing). + */ + public function all() + { + return $this; + } + + /** + * Creates new collection with only visible pages + * + * @return static The collection with only visible pages + * @phpstan-return static + */ + public function visible() + { + $collection = $this->__call('visible', []); + + return $collection; + } + + /** + * Creates new collection with only non-visible pages + * + * @return static The collection with only non-visible pages + * @phpstan-return static + */ + public function nonVisible() + { + $collection = $this->__call('nonVisible', []); + + return $collection; + } + + /** + * Creates new collection with only non-modular pages + * + * @return static The collection with only non-modular pages + * @phpstan-return static + */ + public function pages() + { + $collection = $this->__call('pages', []); + + return $collection; + } + + /** + * Creates new collection with only modular pages + * + * @return static The collection with only modular pages + * @phpstan-return static + */ + public function modules() + { + $collection = $this->__call('modules', []); + + return $collection; + } + + /** + * Creates new collection with only modular pages + * + * @return static The collection with only modular pages + * @phpstan-return static + */ + public function modular() + { + return $this->modules(); + } + + /** + * Creates new collection with only non-modular pages + * + * @return static The collection with only non-modular pages + * @phpstan-return static + */ + public function nonModular() + { + return $this->pages(); + } + + /** + * Creates new collection with only published pages + * + * @return static The collection with only published pages + * @phpstan-return static + */ + public function published() + { + $collection = $this->__call('published', []); + + return $collection; + } + + /** + * Creates new collection with only non-published pages + * + * @return static The collection with only non-published pages + * @phpstan-return static + */ + public function nonPublished() + { + $collection = $this->__call('nonPublished', []); + + return $collection; + } + + /** + * Creates new collection with only routable pages + * + * @return static The collection with only routable pages + * @phpstan-return static + */ + public function routable() + { + $collection = $this->__call('routable', []); + + return $collection; + } + + /** + * Creates new collection with only non-routable pages + * + * @return static The collection with only non-routable pages + * @phpstan-return static + */ + public function nonRoutable() + { + $collection = $this->__call('nonRoutable', []); + + return $collection; + } + + /** + * Creates new collection with only pages of the specified type + * + * @param string $type + * @return static The collection + * @phpstan-return static + */ + public function ofType($type) + { + $collection = $this->__call('ofType', [$type]); + + return $collection; + } + + /** + * Creates new collection with only pages of one of the specified types + * + * @param string[] $types + * @return static The collection + * @phpstan-return static + */ + public function ofOneOfTheseTypes($types) + { + $collection = $this->__call('ofOneOfTheseTypes', [$types]); + + return $collection; + } + + /** + * Creates new collection with only pages of one of the specified access levels + * + * @param array $accessLevels + * @return static The collection + * @phpstan-return static + */ + public function ofOneOfTheseAccessLevels($accessLevels) + { + $collection = $this->__call('ofOneOfTheseAccessLevels', [$accessLevels]); + + return $collection; + } + + /** + * Converts collection into an array. + * + * @return array + */ + public function toArray() + { + return $this->getCollection()->toArray(); + } + + /** + * Get the extended version of this Collection with each page keyed by route + * + * @return array + * @throws Exception + */ + public function toExtendedArray() + { + return $this->getCollection()->toExtendedArray(); + } + +} diff --git a/system/src/Grav/Common/Flex/Types/Pages/PageObject.php b/system/src/Grav/Common/Flex/Types/Pages/PageObject.php new file mode 100644 index 0000000..6ce2c21 --- /dev/null +++ b/system/src/Grav/Common/Flex/Types/Pages/PageObject.php @@ -0,0 +1,744 @@ + true, + 'full_order' => true, + 'filterBy' => true, + 'translated' => false, + ] + parent::getCachedMethods(); + } + + /** + * @return void + */ + public function initialize(): void + { + if (!$this->_initialized) { + Grav::instance()->fireEvent('onPageProcessed', new Event(['page' => $this])); + $this->_initialized = true; + } + } + + /** + * @param string|array $query + * @return Route|null + */ + public function getRoute($query = []): ?Route + { + $path = $this->route(); + if (null === $path) { + return null; + } + + $route = RouteFactory::createFromString($path); + if ($lang = $route->getLanguage()) { + $grav = Grav::instance(); + if (!$grav['config']->get('system.languages.include_default_lang')) { + /** @var Language $language */ + $language = $grav['language']; + if ($lang === $language->getDefault()) { + $route = $route->withLanguage(''); + } + } + } + if (is_array($query)) { + foreach ($query as $key => $value) { + $route = $route->withQueryParam($key, $value); + } + } else { + $route = $route->withAddedPath($query); + } + + return $route; + } + + /** + * @inheritdoc PageInterface + */ + public function getFormValue(string $name, $default = null, string $separator = null) + { + $test = new stdClass(); + + $value = $this->pageContentValue($name, $test); + if ($value !== $test) { + return $value; + } + + switch ($name) { + case 'name': + // TODO: this should not be template! + return $this->getProperty('template'); + case 'route': + $filesystem = Filesystem::getInstance(false); + $key = $filesystem->dirname($this->hasKey() ? '/' . $this->getKey() : '/'); + return $key !== '/' ? $key : null; + case 'full_route': + return $this->hasKey() ? '/' . $this->getKey() : ''; + case 'full_order': + return $this->full_order(); + case 'lang': + return $this->getLanguage() ?? ''; + case 'translations': + return $this->getLanguages(); + } + + return parent::getFormValue($name, $default, $separator); + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::getCacheKey() + */ + public function getCacheKey(): string + { + $cacheKey = parent::getCacheKey(); + if ($cacheKey) { + /** @var Language $language */ + $language = Grav::instance()['language']; + $cacheKey .= '_' . $language->getActive(); + } + + return $cacheKey; + } + + /** + * @param array $variables + * @return array + */ + protected function onBeforeSave(array $variables) + { + $reorder = $variables[0] ?? true; + + $meta = $this->getMetaData(); + if (($meta['copy'] ?? false) === true) { + $this->folder = $this->getKey(); + } + + // Figure out storage path to the new route. + $parentKey = $this->getProperty('parent_key'); + if ($parentKey !== '') { + $parentRoute = $this->getProperty('route'); + + // Root page cannot be moved. + if ($this->root()) { + throw new RuntimeException(sprintf('Root page cannot be moved to %s', $parentRoute)); + } + + // Make sure page isn't being moved under itself. + $key = $this->getStorageKey(); + + /** @var PageObject|null $parent */ + $parent = $parentKey !== false ? $this->getFlexDirectory()->getObject($parentKey, 'storage_key') : null; + if (!$parent) { + // Page cannot be moved to non-existing location. + throw new RuntimeException(sprintf('Page /%s cannot be moved to non-existing path %s', $key, $parentRoute)); + } + + // TODO: make sure that the page doesn't exist yet if moved/copied. + } + + if ($reorder === true && !$this->root()) { + $reorder = $this->_reorder; + } + + // Force automatic reorder if item is supposed to be added to the last. + if (!is_array($reorder) && (int)$this->order() >= 999999) { + $reorder = []; + } + + // Reorder siblings. + $siblings = is_array($reorder) ? ($this->reorderSiblings($reorder) ?? []) : []; + + $data = $this->prepareStorage(); + unset($data['header']); + + foreach ($siblings as $sibling) { + $data = $sibling->prepareStorage(); + unset($data['header']); + } + + return ['reorder' => $reorder, 'siblings' => $siblings]; + } + + /** + * @param array $variables + * @return array + */ + protected function onSave(array $variables): array + { + /** @var PageCollection $siblings */ + $siblings = $variables['siblings']; + /** @var PageObject $sibling */ + foreach ($siblings as $sibling) { + $sibling->save(false); + } + + return $variables; + } + + /** + * @param array $variables + */ + protected function onAfterSave(array $variables): void + { + $this->getFlexDirectory()->reloadIndex(); + } + + /** + * @param UserInterface|null $user + */ + public function check(UserInterface $user = null): void + { + parent::check($user); + + if ($user && $this->isMoved()) { + $parentKey = $this->getProperty('parent_key'); + + /** @var PageObject|null $parent */ + $parent = $this->getFlexDirectory()->getObject($parentKey, 'storage_key'); + if (!$parent || !$parent->isAuthorized('create', null, $user)) { + throw new \RuntimeException('Forbidden', 403); + } + } + } + + /** + * @param array|bool $reorder + * @return static + */ + public function save($reorder = true) + { + $variables = $this->onBeforeSave(func_get_args()); + + // Backwards compatibility with older plugins. + $fireEvents = $reorder && $this->isAdminSite() && $this->getFlexDirectory()->getConfig('object.compat.events', true); + $grav = $this->getContainer(); + if ($fireEvents) { + $self = $this; + $grav->fireEvent('onAdminSave', new Event(['type' => 'flex', 'directory' => $this->getFlexDirectory(), 'object' => &$self])); + if ($self !== $this) { + throw new RuntimeException('Switching Flex Page object during onAdminSave event is not supported! Please update plugin.'); + } + } + + /** @var static $instance */ + $instance = parent::save(); + $variables = $this->onSave($variables); + + $this->onAfterSave($variables); + + // Backwards compatibility with older plugins. + if ($fireEvents) { + $grav->fireEvent('onAdminAfterSave', new Event(['type' => 'flex', 'directory' => $this->getFlexDirectory(), 'object' => $this])); + } + + // Reset original after save events have all been called. + $this->_originalObject = null; + + return $instance; + } + + /** + * @return static + */ + public function delete() + { + $result = parent::delete(); + + // Backwards compatibility with older plugins. + $fireEvents = $this->isAdminSite() && $this->getFlexDirectory()->getConfig('object.compat.events', true); + if ($fireEvents) { + $this->getContainer()->fireEvent('onAdminAfterDelete', new Event(['object' => $this])); + } + + return $result; + } + + /** + * 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 PageInterface $parent New parent page. + * @return $this + */ + public function move(PageInterface $parent) + { + if (!$parent instanceof FlexObjectInterface) { + throw new RuntimeException('Failed: Parent is not Flex Object'); + } + + $this->_reorder = []; + $this->setProperty('parent_key', $parent->getStorageKey()); + $this->storeOriginal(); + + return $this; + } + + /** + * @param UserInterface $user + * @param string $action + * @param string $scope + * @param bool $isMe + * @return bool|null + */ + protected function isAuthorizedOverride(UserInterface $user, string $action, string $scope, bool $isMe): ?bool + { + // Special case: creating a new page means checking parent for its permissions. + if ($action === 'create' && !$this->exists()) { + $parent = $this->parent(); + if ($parent && method_exists($parent, 'isAuthorized')) { + return $parent->isAuthorized($action, $scope, $user); + } + + return false; + } + + return parent::isAuthorizedOverride($user, $action, $scope, $isMe); + } + + /** + * @return bool + */ + protected function isMoved(): bool + { + $storageKey = $this->getMasterKey(); + $filesystem = Filesystem::getInstance(false); + $oldParentKey = ltrim($filesystem->dirname("/{$storageKey}"), '/'); + $newParentKey = $this->getProperty('parent_key'); + + return $this->exists() && $oldParentKey !== $newParentKey; + } + + /** + * @param array $ordering + * @return PageCollection|null + * @phpstan-return ObjectCollection|null + */ + protected function reorderSiblings(array $ordering) + { + $storageKey = $this->getMasterKey(); + $isMoved = $this->isMoved(); + $order = !$isMoved ? $this->order() : false; + if ($order !== false) { + $order = (int)$order; + } + + $parent = $this->parent(); + if (!$parent) { + throw new RuntimeException('Cannot reorder a page which has no parent'); + } + + /** @var PageCollection $siblings */ + $siblings = $parent->children(); + $siblings = $siblings->getCollection()->withOrdered(); + + // Handle special case where ordering isn't given. + if ($ordering === []) { + if ($order >= 999999) { + // Set ordering to point to be the last item, ignoring the object itself. + $order = 0; + foreach ($siblings as $sibling) { + if ($sibling->getKey() !== $this->getKey()) { + $order = max($order, (int)$sibling->order()); + } + } + $this->order($order + 1); + } + + // Do not change sibling ordering. + return null; + } + + $siblings = $siblings->orderBy(['order' => 'ASC']); + + if ($storageKey !== null) { + if ($order !== false) { + // Add current page back to the list if it's ordered. + $siblings->set($storageKey, $this); + } else { + // Remove old copy of the current page from the siblings list. + $siblings->remove($storageKey); + } + } + + // Add missing siblings into the end of the list, keeping the previous ordering between them. + foreach ($siblings as $sibling) { + $folder = (string)$sibling->getProperty('folder'); + $basename = preg_replace('|^\d+\.|', '', $folder); + if (!in_array($basename, $ordering, true)) { + $ordering[] = $basename; + } + } + + // Reorder. + $ordering = array_flip(array_values($ordering)); + $count = count($ordering); + foreach ($siblings as $sibling) { + $folder = (string)$sibling->getProperty('folder'); + $basename = preg_replace('|^\d+\.|', '', $folder); + $newOrder = $ordering[$basename] ?? null; + $newOrder = null !== $newOrder ? $newOrder + 1 : (int)$sibling->order() + $count; + $sibling->order($newOrder); + } + + $siblings = $siblings->orderBy(['order' => 'ASC']); + $siblings->removeElement($this); + + // If menu item was moved, just make it to be the last in order. + if ($isMoved && $this->order() !== false) { + $parentKey = $this->getProperty('parent_key'); + if ($parentKey === '') { + /** @var PageIndex $index */ + $index = $this->getFlexDirectory()->getIndex(); + $newParent = $index->getRoot(); + } else { + $newParent = $this->getFlexDirectory()->getObject($parentKey, 'storage_key'); + if (!$newParent instanceof PageInterface) { + throw new RuntimeException("New parent page '{$parentKey}' not found."); + } + } + /** @var PageCollection $newSiblings */ + $newSiblings = $newParent->children(); + $newSiblings = $newSiblings->getCollection()->withOrdered(); + $order = 0; + foreach ($newSiblings as $sibling) { + $order = max($order, (int)$sibling->order()); + } + $this->order($order + 1); + } + + return $siblings; + } + + /** + * @return string + */ + public function full_order(): string + { + $route = $this->path() . '/' . $this->folder(); + + return preg_replace(PageIndex::ORDER_LIST_REGEX, '\\1', $route) ?? $route; + } + + /** + * @param string $name + * @return Blueprint + */ + protected function doGetBlueprint(string $name = ''): Blueprint + { + try { + // Make sure that pages has been initialized. + Pages::getTypes(); + + // TODO: We need to move raw blueprint logic to Grav itself to remove admin dependency here. + if ($name === 'raw') { + // Admin RAW mode. + if ($this->isAdminSite()) { + /** @var Admin $admin */ + $admin = Grav::instance()['admin']; + + $template = $this->isModule() ? 'modular_raw' : ($this->root() ? 'root_raw' : 'raw'); + + return $admin->blueprints("admin/pages/{$template}"); + } + } + + $template = $this->getProperty('template') . ($name ? '.' . $name : ''); + + $blueprint = $this->getFlexDirectory()->getBlueprint($template, 'blueprints://pages'); + } catch (RuntimeException $e) { + $template = 'default' . ($name ? '.' . $name : ''); + + $blueprint = $this->getFlexDirectory()->getBlueprint($template, 'blueprints://pages'); + } + + $isNew = $blueprint->get('initialized', false) === false; + if ($isNew === true && $name === '') { + // Support onBlueprintCreated event just like in Pages::blueprints($template) + $blueprint->set('initialized', true); + $blueprint->setFilename($template); + + Grav::instance()->fireEvent('onBlueprintCreated', new Event(['blueprint' => $blueprint, 'type' => $template])); + } + + return $blueprint; + } + + /** + * @param array $options + * @return array + */ + public function getLevelListing(array $options): array + { + $index = $this->getFlexDirectory()->getIndex(); + if (!is_callable([$index, 'getLevelListing'])) { + return []; + } + + // Deal with relative paths. + $initial = $options['initial'] ?? null; + $var = $initial ? 'leaf_route' : 'route'; + $route = $options[$var] ?? ''; + if ($route !== '' && !str_starts_with($route, '/')) { + $filesystem = Filesystem::getInstance(); + + $route = "/{$this->getKey()}/{$route}"; + $route = $filesystem->normalize($route); + + $options[$var] = $route; + } + + [$status, $message, $response,] = $index->getLevelListing($options); + + return [$status, $message, $response, $options[$var] ?? null]; + } + + /** + * Filter page (true/false) by given filters. + * + * - search: string + * - extension: string + * - module: bool + * - visible: bool + * - routable: bool + * - published: bool + * - page: bool + * - translated: bool + * + * @param array $filters + * @param bool $recursive + * @return bool + */ + public function filterBy(array $filters, bool $recursive = false): bool + { + $language = $filters['language'] ?? null; + if (null !== $language) { + /** @var PageObject $test */ + $test = $this->getTranslation($language) ?? $this; + } else { + $test = $this; + } + + foreach ($filters as $key => $value) { + switch ($key) { + case 'search': + $matches = $test->search((string)$value) > 0.0; + break; + case 'page_type': + $types = $value ? explode(',', $value) : []; + $matches = in_array($test->template(), $types, true); + break; + case 'extension': + $matches = Utils::contains((string)$value, $test->extension()); + break; + case 'routable': + $matches = $test->isRoutable() === (bool)$value; + break; + case 'published': + $matches = $test->isPublished() === (bool)$value; + break; + case 'visible': + $matches = $test->isVisible() === (bool)$value; + break; + case 'module': + $matches = $test->isModule() === (bool)$value; + break; + case 'page': + $matches = $test->isPage() === (bool)$value; + break; + case 'folder': + $matches = $test->isPage() === !$value; + break; + case 'translated': + $matches = $test->hasTranslation() === (bool)$value; + break; + default: + $matches = true; + break; + } + + // If current filter does not match, we still may have match as a parent. + if ($matches === false) { + if (!$recursive) { + return false; + } + + /** @var PageIndex $index */ + $index = $this->children()->getIndex(); + + return $index->filterBy($filters, true)->count() > 0; + } + } + + return true; + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::exists() + */ + public function exists(): bool + { + return $this->root ?: parent::exists(); + } + + /** + * @return array + */ + public function __debugInfo(): array + { + $list = parent::__debugInfo(); + + return $list + [ + '_content_meta:private' => $this->getContentMeta(), + '_content:private' => $this->getRawContent() + ]; + } + + /** + * @param array $elements + * @param bool $extended + */ + protected function filterElements(array &$elements, bool $extended = false): void + { + // Change parent page if needed. + if (array_key_exists('route', $elements) && isset($elements['folder'], $elements['name'])) { + $elements['template'] = $elements['name']; + + // Figure out storage path to the new route. + $parentKey = trim($elements['route'] ?? '', '/'); + if ($parentKey !== '') { + /** @var PageObject|null $parent */ + $parent = $this->getFlexDirectory()->getObject($parentKey); + $parentKey = $parent ? $parent->getStorageKey() : $parentKey; + } + + $elements['parent_key'] = $parentKey; + } + + // Deal with ordering=bool and order=page1,page2,page3. + if ($this->root()) { + // Root page doesn't have ordering. + unset($elements['ordering'], $elements['order']); + } elseif (array_key_exists('ordering', $elements) && array_key_exists('order', $elements)) { + // Store ordering. + $ordering = $elements['order'] ?? null; + $this->_reorder = !empty($ordering) ? explode(',', $ordering) : []; + + $order = false; + if ((bool)($elements['ordering'] ?? false)) { + $order = $this->order(); + if ($order === false) { + $order = 999999; + } + } + + $elements['order'] = $order; + } + + parent::filterElements($elements, true); + } + + /** + * @return array + */ + public function prepareStorage(): array + { + $meta = $this->getMetaData(); + $oldLang = $meta['lang'] ?? ''; + $newLang = $this->getProperty('lang') ?? ''; + + // Always clone the page to the new language. + if ($oldLang !== $newLang) { + $meta['clone'] = true; + } + + // Make sure that certain elements are always sent to the storage layer. + $elements = [ + '__META' => $meta, + 'storage_key' => $this->getStorageKey(), + 'parent_key' => $this->getProperty('parent_key'), + 'order' => $this->getProperty('order'), + 'folder' => preg_replace('|^\d+\.|', '', $this->getProperty('folder') ?? ''), + 'template' => preg_replace('|modular/|', '', $this->getProperty('template') ?? ''), + 'lang' => $newLang + ] + parent::prepareStorage(); + + return $elements; + } +} diff --git a/system/src/Grav/Common/Flex/Types/Pages/Storage/PageStorage.php b/system/src/Grav/Common/Flex/Types/Pages/Storage/PageStorage.php new file mode 100644 index 0000000..72349dc --- /dev/null +++ b/system/src/Grav/Common/Flex/Types/Pages/Storage/PageStorage.php @@ -0,0 +1,700 @@ +flags = FilesystemIterator::KEY_AS_FILENAME | FilesystemIterator::CURRENT_AS_FILEINFO + | FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS; + + $grav = Grav::instance(); + + $config = $grav['config']; + $this->ignore_hidden = (bool)$config->get('system.pages.ignore_hidden'); + $this->ignore_files = (array)$config->get('system.pages.ignore_files'); + $this->ignore_folders = (array)$config->get('system.pages.ignore_folders'); + $this->include_default_lang_file_extension = (bool)$config->get('system.languages.include_default_lang_file_extension', true); + $this->recurse = (bool)($options['recurse'] ?? true); + $this->regex = '/(\.([\w\d_-]+))?\.md$/D'; + } + + /** + * @param string $key + * @param bool $variations + * @return array + */ + public function parseKey(string $key, bool $variations = true): array + { + if (mb_strpos($key, '|') !== false) { + [$key, $params] = explode('|', $key, 2); + } else { + $params = ''; + } + $key = ltrim($key, '/'); + + $keys = parent::parseKey($key, false) + ['params' => $params]; + + if ($variations) { + $keys += $this->parseParams($key, $params); + } + + return $keys; + } + + /** + * @param string $key + * @return string + */ + public function readFrontmatter(string $key): string + { + $path = $this->getPathFromKey($key); + $file = $this->getFile($path); + try { + if ($file instanceof MarkdownFile) { + $frontmatter = $file->frontmatter(); + } else { + $frontmatter = $file->raw(); + } + } catch (RuntimeException $e) { + $frontmatter = 'ERROR: ' . $e->getMessage(); + } finally { + $file->free(); + unset($file); + } + + return $frontmatter; + } + + /** + * @param string $key + * @return string + */ + public function readRaw(string $key): string + { + $path = $this->getPathFromKey($key); + $file = $this->getFile($path); + try { + $raw = $file->raw(); + } catch (RuntimeException $e) { + $raw = 'ERROR: ' . $e->getMessage(); + } finally { + $file->free(); + unset($file); + } + + return $raw; + } + + /** + * @param array $keys + * @param bool $includeParams + * @return string + */ + public function buildStorageKey(array $keys, bool $includeParams = true): string + { + $key = $keys['key'] ?? null; + if (null === $key) { + $key = $keys['parent_key'] ?? ''; + if ($key !== '') { + $key .= '/'; + } + $order = $keys['order'] ?? null; + $folder = $keys['folder'] ?? 'undefined'; + $key .= is_numeric($order) ? sprintf('%02d.%s', $order, $folder) : $folder; + } + + $params = $includeParams ? $this->buildStorageKeyParams($keys) : ''; + + return $params ? "{$key}|{$params}" : $key; + } + + /** + * @param array $keys + * @return string + */ + public function buildStorageKeyParams(array $keys): string + { + $params = $keys['template'] ?? ''; + $language = $keys['lang'] ?? ''; + if ($language) { + $params .= '.' . $language; + } + + return $params; + } + + /** + * @param array $keys + * @return string + */ + public function buildFolder(array $keys): string + { + return $this->dataFolder . '/' . $this->buildStorageKey($keys, false); + } + + /** + * @param array $keys + * @return string + */ + public function buildFilename(array $keys): string + { + $file = $this->buildStorageKeyParams($keys); + + // Template is optional; if it is missing, we need to have to load the object metadata. + if ($file && $file[0] === '.') { + $meta = $this->getObjectMeta($this->buildStorageKey($keys, false)); + $file = ($meta['template'] ?? 'folder') . $file; + } + + return $file . $this->dataExt; + } + + /** + * @param array $keys + * @return string + */ + public function buildFilepath(array $keys): string + { + $folder = $this->buildFolder($keys); + $filename = $this->buildFilename($keys); + + return rtrim($folder, '/') !== $folder ? $folder . $filename : $folder . '/' . $filename; + } + + /** + * @param array $row + * @param bool $setDefaultLang + * @return array + */ + public function extractKeysFromRow(array $row, bool $setDefaultLang = true): array + { + $meta = $row['__META'] ?? null; + $storageKey = $row['storage_key'] ?? $meta['storage_key'] ?? ''; + $keyMeta = $storageKey !== '' ? $this->extractKeysFromStorageKey($storageKey) : null; + $parentKey = $row['parent_key'] ?? $meta['parent_key'] ?? $keyMeta['parent_key'] ?? ''; + $order = $row['order'] ?? $meta['order'] ?? $keyMeta['order'] ?? null; + $folder = $row['folder'] ?? $meta['folder'] ?? $keyMeta['folder'] ?? ''; + $template = $row['template'] ?? $meta['template'] ?? $keyMeta['template'] ?? ''; + $lang = $row['lang'] ?? $meta['lang'] ?? $keyMeta['lang'] ?? ''; + + // Handle default language, if it should be saved without language extension. + if ($setDefaultLang && empty($meta['markdown'][$lang])) { + $grav = Grav::instance(); + + /** @var Language $language */ + $language = $grav['language']; + $default = $language->getDefault(); + // Make sure that the default language file doesn't exist before overriding it. + if (empty($meta['markdown'][$default])) { + if ($this->include_default_lang_file_extension) { + if ($lang === '') { + $lang = $language->getDefault(); + } + } elseif ($lang === $language->getDefault()) { + $lang = ''; + } + } + } + + $keys = [ + 'key' => null, + 'params' => null, + 'parent_key' => $parentKey, + 'order' => is_numeric($order) ? (int)$order : null, + 'folder' => $folder, + 'template' => $template, + 'lang' => $lang + ]; + + $keys['key'] = $this->buildStorageKey($keys, false); + $keys['params'] = $this->buildStorageKeyParams($keys); + + return $keys; + } + + /** + * @param string $key + * @return array + */ + public function extractKeysFromStorageKey(string $key): array + { + if (mb_strpos($key, '|') !== false) { + [$key, $params] = explode('|', $key, 2); + [$template, $language] = mb_strpos($params, '.') !== false ? explode('.', $params, 2) : [$params, '']; + } else { + $params = $template = $language = ''; + } + $objectKey = Utils::basename($key); + if (preg_match('|^(\d+)\.(.+)$|', $objectKey, $matches)) { + [, $order, $folder] = $matches; + } else { + [$order, $folder] = ['', $objectKey]; + } + + $filesystem = Filesystem::getInstance(false); + + $parentKey = ltrim($filesystem->dirname('/' . $key), '/'); + + return [ + 'key' => $key, + 'params' => $params, + 'parent_key' => $parentKey, + 'order' => is_numeric($order) ? (int)$order : null, + 'folder' => $folder, + 'template' => $template, + 'lang' => $language + ]; + } + + /** + * @param string $key + * @param string $params + * @return array + */ + protected function parseParams(string $key, string $params): array + { + if (mb_strpos($params, '.') !== false) { + [$template, $language] = explode('.', $params, 2); + } else { + $template = $params; + $language = ''; + } + + if ($template === '') { + $meta = $this->getObjectMeta($key); + $template = $meta['template'] ?? 'folder'; + } + + return [ + 'file' => $template . ($language ? '.' . $language : ''), + 'template' => $template, + 'lang' => $language + ]; + } + + /** + * Prepares the row for saving and returns the storage key for the record. + * + * @param array $row + */ + protected function prepareRow(array &$row): void + { + // Remove keys used in the filesystem. + unset($row['parent_key'], $row['order'], $row['folder'], $row['template'], $row['lang']); + } + + /** + * @param string $key + * @return array + */ + protected function loadRow(string $key): ?array + { + $data = parent::loadRow($key); + + // Special case for root page. + if ($key === '' && null !== $data) { + $data['root'] = true; + } + + return $data; + } + + /** + * Page storage supports moving and copying the pages and their languages. + * + * $row['__META']['copy'] = true Use this if you want to copy the whole folder, otherwise it will be moved + * $row['__META']['clone'] = true Use this if you want to clone the file, otherwise it will be renamed + * + * @param string $key + * @param array $row + * @return array + */ + protected function saveRow(string $key, array $row): array + { + // Initialize all key-related variables. + $newKeys = $this->extractKeysFromRow($row); + $newKey = $this->buildStorageKey($newKeys); + $newFolder = $this->buildFolder($newKeys); + $newFilename = $this->buildFilename($newKeys); + $newFilepath = rtrim($newFolder, '/') !== $newFolder ? $newFolder . $newFilename : $newFolder . '/' . $newFilename; + + try { + if ($key === '' && empty($row['root'])) { + throw new RuntimeException('Page has no path'); + } + + $grav = Grav::instance(); + + /** @var Debugger $debugger */ + $debugger = $grav['debugger']; + $debugger->addMessage("Save page: {$newKey}", 'debug'); + + // Check if the row already exists. + $oldKey = $row['__META']['storage_key'] ?? null; + if (is_string($oldKey)) { + // Initialize all old key-related variables. + $oldKeys = $this->extractKeysFromRow(['__META' => $row['__META']], false); + $oldFolder = $this->buildFolder($oldKeys); + $oldFilename = $this->buildFilename($oldKeys); + + // Check if folder has changed. + if ($oldFolder !== $newFolder && file_exists($oldFolder)) { + $isCopy = $row['__META']['copy'] ?? false; + if ($isCopy) { + if (strpos($newFolder, $oldFolder . '/') === 0) { + throw new RuntimeException(sprintf('Page /%s cannot be copied to itself', $oldKey)); + } + + $this->copyRow($oldKey, $newKey); + $debugger->addMessage("Page copied: {$oldFolder} => {$newFolder}", 'debug'); + } else { + if (strpos($newFolder, $oldFolder . '/') === 0) { + throw new RuntimeException(sprintf('Page /%s cannot be moved to itself', $oldKey)); + } + + $this->renameRow($oldKey, $newKey); + $debugger->addMessage("Page moved: {$oldFolder} => {$newFolder}", 'debug'); + } + } + + // Check if filename has changed. + if ($oldFilename !== $newFilename) { + // Get instance of the old file (we have already copied/moved it). + $oldFilepath = "{$newFolder}/{$oldFilename}"; + $file = $this->getFile($oldFilepath); + + // Rename the file if we aren't supposed to clone it. + $isClone = $row['__META']['clone'] ?? false; + if (!$isClone && $file->exists()) { + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + $toPath = $locator->isStream($newFilepath) ? $locator->findResource($newFilepath, true, true) : GRAV_ROOT . "/{$newFilepath}"; + $success = $file->rename($toPath); + if (!$success) { + throw new RuntimeException("Changing page template failed: {$oldFilepath} => {$newFilepath}"); + } + $debugger->addMessage("Page template changed: {$oldFilename} => {$newFilename}", 'debug'); + } else { + $file = null; + $debugger->addMessage("Page template created: {$newFilename}", 'debug'); + } + } + } + + // Clean up the data to be saved. + $this->prepareRow($row); + unset($row['__META'], $row['__ERROR']); + + if (!isset($file)) { + $file = $this->getFile($newFilepath); + } + + // Compare existing file content to the new one and save the file only if content has been changed. + $file->free(); + $oldRaw = $file->raw(); + $file->content($row); + $newRaw = $file->raw(); + if ($oldRaw !== $newRaw) { + $file->save($row); + $debugger->addMessage("Page content saved: {$newFilepath}", 'debug'); + } else { + $debugger->addMessage('Page content has not been changed, do not update the file', 'debug'); + } + } catch (RuntimeException $e) { + $name = isset($file) ? $file->filename() : $newKey; + + throw new RuntimeException(sprintf('Flex saveRow(%s): %s', $name, $e->getMessage())); + } finally { + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + $locator->clearCache(); + + if (isset($file)) { + $file->free(); + unset($file); + } + } + + $row['__META'] = $this->getObjectMeta($newKey, true); + + return $row; + } + + /** + * Check if page folder should be deleted. + * + * Deleting page can be done either by deleting everything or just a single language. + * If key contains the language, delete only it, unless it is the last language. + * + * @param string $key + * @return bool + */ + protected function canDeleteFolder(string $key): bool + { + // Return true if there's no language in the key. + $keys = $this->extractKeysFromStorageKey($key); + if (!$keys['lang']) { + return true; + } + + // Get the main key and reload meta. + $key = $this->buildStorageKey($keys); + $meta = $this->getObjectMeta($key, true); + + // Return true if there aren't any markdown files left. + return empty($meta['markdown'] ?? []); + } + + /** + * Get key from the filesystem path. + * + * @param string $path + * @return string + */ + protected function getKeyFromPath(string $path): string + { + if ($this->base_path) { + $path = $this->base_path . '/' . $path; + } + + return $path; + } + + /** + * Returns list of all stored keys in [key => timestamp] pairs. + * + * @return array + */ + protected function buildIndex(): array + { + $this->clearCache(); + + return $this->getIndexMeta(); + } + + /** + * @param string $key + * @param bool $reload + * @return array + */ + protected function getObjectMeta(string $key, bool $reload = false): array + { + $keys = $this->extractKeysFromStorageKey($key); + $key = $keys['key']; + + if ($reload || !isset($this->meta[$key])) { + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + if (mb_strpos($key, '@@') === false) { + $path = $this->getStoragePath($key); + if (is_string($path)) { + $path = $locator->isStream($path) ? $locator->findResource($path) : GRAV_ROOT . "/{$path}"; + } else { + $path = null; + } + } else { + $path = null; + } + + $modified = 0; + $markdown = []; + $children = []; + + if (is_string($path) && is_dir($path)) { + $modified = filemtime($path); + $iterator = new FilesystemIterator($path, $this->flags); + + /** @var SplFileInfo $info */ + foreach ($iterator as $k => $info) { + // Ignore all hidden files if set. + if ($k === '' || ($this->ignore_hidden && $k[0] === '.')) { + continue; + } + + if ($info->isDir()) { + // Ignore all folders in ignore list. + if ($this->ignore_folders && in_array($k, $this->ignore_folders, true)) { + continue; + } + + $children[$k] = false; + } else { + // Ignore all files in ignore list. + if ($this->ignore_files && in_array($k, $this->ignore_files, true)) { + continue; + } + + $timestamp = $info->getMTime(); + + // Page is the one that matches to $page_extensions list with the lowest index number. + if (preg_match($this->regex, $k, $matches)) { + $mark = $matches[2] ?? ''; + $ext = $matches[1] ?? ''; + $ext .= $this->dataExt; + $markdown[$mark][Utils::basename($k, $ext)] = $timestamp; + } + + $modified = max($modified, $timestamp); + } + } + } + + $rawRoute = trim(preg_replace(PageIndex::PAGE_ROUTE_REGEX, '/', "/{$key}") ?? '', '/'); + $route = PageIndex::normalizeRoute($rawRoute); + + ksort($markdown, SORT_NATURAL | SORT_FLAG_CASE); + ksort($children, SORT_NATURAL | SORT_FLAG_CASE); + + $file = array_key_first($markdown[''] ?? (reset($markdown) ?: [])); + + $meta = [ + 'key' => $route, + 'storage_key' => $key, + 'template' => $file, + 'storage_timestamp' => $modified, + ]; + if ($markdown) { + $meta['markdown'] = $markdown; + } + if ($children) { + $meta['children'] = $children; + } + $meta['checksum'] = md5(json_encode($meta) ?: ''); + + // Cache meta as copy. + $this->meta[$key] = $meta; + } else { + $meta = $this->meta[$key]; + } + + $params = $keys['params']; + if ($params) { + $language = $keys['lang']; + $template = $keys['template'] ?: array_key_first($meta['markdown'][$language]) ?? $meta['template']; + $meta['exists'] = ($template && !empty($meta['children'])) || isset($meta['markdown'][$language][$template]); + $meta['storage_key'] .= '|' . $params; + $meta['template'] = $template; + $meta['lang'] = $language; + } + + return $meta; + } + + /** + * @return array + */ + protected function getIndexMeta(): array + { + $queue = ['']; + $list = []; + do { + $current = array_pop($queue); + if ($current === null) { + break; + } + + $meta = $this->getObjectMeta($current); + $storage_key = $meta['storage_key']; + + if (!empty($meta['children'])) { + $prefix = $storage_key . ($storage_key !== '' ? '/' : ''); + + foreach ($meta['children'] as $child => $value) { + $queue[] = $prefix . $child; + } + } + + $list[$storage_key] = $meta; + } while ($queue); + + ksort($list, SORT_NATURAL | SORT_FLAG_CASE); + + // Update parent timestamps. + foreach (array_reverse($list) as $storage_key => $meta) { + if ($storage_key !== '') { + $filesystem = Filesystem::getInstance(false); + + $storage_key = (string)$storage_key; + $parentKey = $filesystem->dirname($storage_key); + if ($parentKey === '.') { + $parentKey = ''; + } + + /** @phpstan-var array{'storage_key': string, 'storage_timestamp': int, 'children': array} $parent */ + $parent = &$list[$parentKey]; + $basename = Utils::basename($storage_key); + + if (isset($parent['children'][$basename])) { + $timestamp = $meta['storage_timestamp']; + $parent['children'][$basename] = $timestamp; + if ($basename && $basename[0] === '_') { + $parent['storage_timestamp'] = max($parent['storage_timestamp'], $timestamp); + } + } + } + } + + return $list; + } + + /** + * @return string + */ + protected function getNewKey(): string + { + throw new RuntimeException('Generating random key is disabled for pages'); + } +} diff --git a/system/src/Grav/Common/Flex/Types/Pages/Traits/PageContentTrait.php b/system/src/Grav/Common/Flex/Types/Pages/Traits/PageContentTrait.php new file mode 100644 index 0000000..95d14f9 --- /dev/null +++ b/system/src/Grav/Common/Flex/Types/Pages/Traits/PageContentTrait.php @@ -0,0 +1,75 @@ +getProperty($property) : null; + if (null === $value) { + $value = $this->language() . ($var ?? ($this->modified() . md5($this->filePath() ?? $this->getKey()))); + + $this->setProperty($property, $value); + if ($this->doHasProperty($property)) { + $value = $this->getProperty($property); + } + } + + return $value; + } + + + /** + * @inheritdoc + */ + public function date($var = null): int + { + return $this->loadHeaderProperty( + 'date', + $var, + function ($value) { + $value = $value ? Utils::date2timestamp($value, $this->getProperty('dateformat')) : false; + + if (!$value) { + // Get the specific translation updated date. + $meta = $this->getMetaData(); + $language = $meta['lang'] ?? ''; + $template = $this->getProperty('template'); + $value = $meta['markdown'][$language][$template] ?? 0; + } + + return $value ?: $this->modified(); + } + ); + } + + /** + * @inheritdoc + * @param bool $bool + */ + public function isPage(bool $bool = true): bool + { + $meta = $this->getMetaData(); + + return empty($meta['markdown']) !== $bool; + } +} diff --git a/system/src/Grav/Common/Flex/Types/Pages/Traits/PageLegacyTrait.php b/system/src/Grav/Common/Flex/Types/Pages/Traits/PageLegacyTrait.php new file mode 100644 index 0000000..cd0f887 --- /dev/null +++ b/system/src/Grav/Common/Flex/Types/Pages/Traits/PageLegacyTrait.php @@ -0,0 +1,236 @@ +path() ?? ''; + + return $pages->children($path); + } + + /** + * Check to see if this item is the first in an array of sub-pages. + * + * @return bool True if item is first. + */ + public function isFirst(): bool + { + if (Utils::isAdminPlugin()) { + return parent::isFirst(); + } + + $path = $this->path(); + $parent = $this->parent(); + $collection = $parent ? $parent->collection('content', false) : null; + if (null !== $path && $collection instanceof PageCollectionInterface) { + return $collection->isFirst($path); + } + + return true; + } + + /** + * Check to see if this item is the last in an array of sub-pages. + * + * @return bool True if item is last + */ + public function isLast(): bool + { + if (Utils::isAdminPlugin()) { + return parent::isLast(); + } + + $path = $this->path(); + $parent = $this->parent(); + $collection = $parent ? $parent->collection('content', false) : null; + if (null !== $path && $collection instanceof PageCollectionInterface) { + return $collection->isLast($path); + } + + return true; + } + + /** + * Returns the adjacent sibling based on a direction. + * + * @param int $direction either -1 or +1 + * @return PageInterface|false the sibling page + */ + public function adjacentSibling($direction = 1) + { + if (Utils::isAdminPlugin()) { + return parent::adjacentSibling($direction); + } + + $path = $this->path(); + $parent = $this->parent(); + $collection = $parent ? $parent->collection('content', false) : null; + if (null !== $path && $collection instanceof PageCollectionInterface) { + $child = $collection->adjacentSibling($path, $direction); + if ($child instanceof PageInterface) { + return $child; + } + } + + return false; + } + + /** + * Helper method to return an ancestor page. + * + * @param string|null $lookup Name of the parent folder + * @return PageInterface|null page you were looking for if it exists + */ + public function ancestor($lookup = null) + { + if (Utils::isAdminPlugin()) { + return parent::ancestor($lookup); + } + + /** @var Pages $pages */ + $pages = Grav::instance()['pages']; + + return $pages->ancestor($this->getProperty('parent_route'), $lookup); + } + + /** + * Method that contains shared logic for inherited() and inheritedField() + * + * @param string $field Name of the parent folder + * @return array + */ + protected function getInheritedParams($field): array + { + if (Utils::isAdminPlugin()) { + return parent::getInheritedParams($field); + } + + /** @var Pages $pages */ + $pages = Grav::instance()['pages']; + + $inherited = $pages->inherited($this->getProperty('parent_route'), $field); + $inheritedParams = $inherited ? (array)$inherited->value('header.' . $field) : []; + $currentParams = (array)$this->getFormValue('header.' . $field); + if ($inheritedParams && is_array($inheritedParams)) { + $currentParams = array_replace_recursive($inheritedParams, $currentParams); + } + + return [$inherited, $currentParams]; + } + + /** + * Helper method to return a page. + * + * @param string $url the url of the page + * @param bool $all + * @return PageInterface|null page you were looking for if it exists + */ + public function find($url, $all = false) + { + if (Utils::isAdminPlugin()) { + return parent::find($url, $all); + } + + /** @var Pages $pages */ + $pages = Grav::instance()['pages']; + + return $pages->find($url, $all); + } + + /** + * Get a collection of pages in the current context. + * + * @param string|array $params + * @param bool $pagination + * @return PageCollectionInterface|Collection + * @throws InvalidArgumentException + */ + public function collection($params = 'content', $pagination = true) + { + if (Utils::isAdminPlugin()) { + return parent::collection($params, $pagination); + } + + if (is_string($params)) { + // Look into a page header field. + $params = (array)$this->getFormValue('header.' . $params); + } elseif (!is_array($params)) { + throw new InvalidArgumentException('Argument should be either header variable name or array of parameters'); + } + + $context = [ + 'pagination' => $pagination, + 'self' => $this + ]; + + /** @var Pages $pages */ + $pages = Grav::instance()['pages']; + + return $pages->getCollection($params, $context); + } + + /** + * @param string|array $value + * @param bool $only_published + * @return PageCollectionInterface|Collection + */ + public function evaluate($value, $only_published = true) + { + if (Utils::isAdminPlugin()) { + return parent::collection($value, $only_published); + } + + $params = [ + 'items' => $value, + 'published' => $only_published + ]; + $context = [ + 'event' => false, + 'pagination' => false, + 'url_taxonomy_filters' => false, + 'self' => $this + ]; + + /** @var Pages $pages */ + $pages = Grav::instance()['pages']; + + return $pages->getCollection($params, $context); + } +} diff --git a/system/src/Grav/Common/Flex/Types/Pages/Traits/PageRoutableTrait.php b/system/src/Grav/Common/Flex/Types/Pages/Traits/PageRoutableTrait.php new file mode 100644 index 0000000..f3defb5 --- /dev/null +++ b/system/src/Grav/Common/Flex/Types/Pages/Traits/PageRoutableTrait.php @@ -0,0 +1,122 @@ +root()) { + return null; + } + + /** @var Pages $pages */ + $pages = Grav::instance()['pages']; + + $filesystem = Filesystem::getInstance(false); + + // FIXME: this does not work, needs to use $pages->get() with cached parent id! + $key = $this->getKey(); + $parent_route = $filesystem->dirname('/' . $key); + + return $parent_route !== '/' ? $pages->find($parent_route) : $pages->root(); + } + + /** + * Returns the item in the current position. + * + * @return int|null the index of the current page. + */ + public function currentPosition(): ?int + { + $path = $this->path(); + $parent = $this->parent(); + $collection = $parent ? $parent->collection('content', false) : null; + if (null !== $path && $collection instanceof PageCollectionInterface) { + return $collection->currentPosition($path); + } + + return 1; + } + + /** + * 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(): bool + { + $grav = Grav::instance(); + $uri_path = rtrim(urldecode($grav['uri']->path()), '/') ?: '/'; + $routes = $grav['pages']->routes(); + + return isset($routes[$uri_path]) && $routes[$uri_path] === $this->path(); + } + + /** + * 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(): bool + { + $grav = Grav::instance(); + /** @var Uri $uri */ + $uri = $grav['uri']; + /** @var Pages $pages */ + $pages = $grav['pages']; + $uri_path = rtrim(urldecode($uri->path()), '/'); + $routes = $pages->routes(); + + if (isset($routes[$uri_path])) { + $page = $pages->find($uri->route()); + /** @var PageInterface|null $child_page */ + $child_page = $page ? $page->parent() : null; + while ($child_page && !$child_page->root()) { + if ($this->path() === $child_page->path()) { + return true; + } + $child_page = $child_page->parent(); + } + } + + return false; + } +} diff --git a/system/src/Grav/Common/Flex/Types/Pages/Traits/PageTranslateTrait.php b/system/src/Grav/Common/Flex/Types/Pages/Traits/PageTranslateTrait.php new file mode 100644 index 0000000..12cf6d5 --- /dev/null +++ b/system/src/Grav/Common/Flex/Types/Pages/Traits/PageTranslateTrait.php @@ -0,0 +1,108 @@ +getLanguageTemplates(); + if (!$translated) { + return $translated; + } + + $grav = Grav::instance(); + + /** @var Language $language */ + $language = $grav['language']; + + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + + $languages = $language->getLanguages(); + $languages[] = ''; + $defaultCode = $language->getDefault(); + + if (isset($translated[$defaultCode])) { + unset($translated['']); + } + + foreach ($translated as $key => &$template) { + $template .= $key !== '' ? ".{$key}.md" : '.md'; + } + unset($template); + + $translated = array_intersect_key($translated, array_flip($languages)); + + $folder = $this->getStorageFolder(); + if (!$folder) { + return []; + } + $folder = $locator->isStream($folder) ? $locator->getResource($folder) : GRAV_ROOT . "/{$folder}"; + + $list = array_fill_keys($languages, null); + foreach ($translated as $languageCode => $languageFile) { + $languageExtension = $languageCode ? ".{$languageCode}.md" : '.md'; + $path = "{$folder}/{$languageFile}"; + + // FIXME: use flex, also rawRoute() does not fully work? + $aPage = new Page(); + $aPage->init(new SplFileInfo($path), $languageExtension); + if ($onlyPublished && !$aPage->published()) { + continue; + } + + $header = $aPage->header(); + // @phpstan-ignore-next-line + $routes = $header->routes ?? []; + $route = $routes['default'] ?? $aPage->rawRoute(); + if (!$route) { + $route = $aPage->route(); + } + + $list[$languageCode ?: $defaultCode] = $route ?? ''; + } + + $list = array_filter($list, static function ($var) { + return null !== $var; + }); + + // Hack to get the same result as with old pages. + foreach ($list as &$path) { + if ($path === '') { + $path = null; + } + } + + return $list; + } +} diff --git a/system/src/Grav/Common/Flex/Types/UserGroups/UserGroupCollection.php b/system/src/Grav/Common/Flex/Types/UserGroups/UserGroupCollection.php new file mode 100644 index 0000000..3be0e13 --- /dev/null +++ b/system/src/Grav/Common/Flex/Types/UserGroups/UserGroupCollection.php @@ -0,0 +1,56 @@ + + */ +class UserGroupCollection extends FlexCollection +{ + /** + * @return array + */ + public static function getCachedMethods(): array + { + return [ + 'authorize' => false, + ] + parent::getCachedMethods(); + } + + /** + * Checks user authorization to the action. + * + * @param string $action + * @param string|null $scope + * @return bool|null + */ + public function authorize(string $action, string $scope = null): ?bool + { + $authorized = null; + /** @var UserGroupObject $object */ + foreach ($this as $object) { + $auth = $object->authorize($action, $scope); + if ($auth === true) { + $authorized = true; + } elseif ($auth === false) { + return false; + } + } + + return $authorized; + } +} diff --git a/system/src/Grav/Common/Flex/Types/UserGroups/UserGroupIndex.php b/system/src/Grav/Common/Flex/Types/UserGroups/UserGroupIndex.php new file mode 100644 index 0000000..ac27eff --- /dev/null +++ b/system/src/Grav/Common/Flex/Types/UserGroups/UserGroupIndex.php @@ -0,0 +1,24 @@ + + */ +class UserGroupIndex extends FlexIndex +{ +} diff --git a/system/src/Grav/Common/Flex/Types/UserGroups/UserGroupObject.php b/system/src/Grav/Common/Flex/Types/UserGroups/UserGroupObject.php new file mode 100644 index 0000000..7a95a7d --- /dev/null +++ b/system/src/Grav/Common/Flex/Types/UserGroups/UserGroupObject.php @@ -0,0 +1,134 @@ + false, + ] + parent::getCachedMethods(); + } + + /** + * @return string + */ + public function getTitle(): string + { + return $this->getProperty('readableName'); + } + + /** + * Checks user authorization to the action. + * + * @param string $action + * @param string|null $scope + * @return bool|null + */ + public function authorize(string $action, string $scope = null): ?bool + { + if ($scope === 'test') { + $scope = null; + } elseif (!$this->getProperty('enabled', true)) { + return null; + } + + $access = $this->getAccess(); + + $authorized = $access->authorize($action, $scope); + if (is_bool($authorized)) { + return $authorized; + } + + return $access->authorize('admin.super') ? true : null; + } + + public static function groupNames(): array + { + $groups = []; + $user_groups = Grav::instance()['user_groups'] ?? []; + + foreach ($user_groups as $key => $group) { + $groups[$key] = $group->readableName; + } + + return $groups; + } + + /** + * @return Access + */ + protected function getAccess(): Access + { + if (null === $this->_access) { + $this->getProperty('access'); + } + + return $this->_access; + } + + /** + * @param mixed $value + * @return array + */ + protected function offsetLoad_access($value): array + { + if (!$value instanceof Access) { + $value = new Access($value); + } + + $this->_access = $value; + + return $value->jsonSerialize(); + } + + /** + * @param mixed $value + * @return array + */ + protected function offsetPrepare_access($value): array + { + return $this->offsetLoad_access($value); + } + + /** + * @param array|null $value + * @return array|null + */ + protected function offsetSerialize_access(?array $value): ?array + { + return $value; + } +} diff --git a/system/src/Grav/Common/Flex/Types/Users/Storage/UserFileStorage.php b/system/src/Grav/Common/Flex/Types/Users/Storage/UserFileStorage.php new file mode 100644 index 0000000..79c5112 --- /dev/null +++ b/system/src/Grav/Common/Flex/Types/Users/Storage/UserFileStorage.php @@ -0,0 +1,47 @@ +update($data)` instead (same but with data validation & filtering, file upload support). + */ + public function merge(array $data) + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->update($data) method instead', E_USER_DEPRECATED); + + $this->setElements($this->getBlueprint()->mergeData($this->toArray(), $data)); + + return $this; + } + + /** + * Return media object for the User's avatar. + * + * @return ImageMedium|StaticImageMedium|null + * @deprecated 1.6 Use ->getAvatarImage() method instead. + */ + public function getAvatarMedia() + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->getAvatarImage() method instead', E_USER_DEPRECATED); + + return $this->getAvatarImage(); + } + + /** + * Return the User's avatar URL + * + * @return string + * @deprecated 1.6 Use ->getAvatarUrl() method instead. + */ + public function avatarUrl() + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->getAvatarUrl() method instead', E_USER_DEPRECATED); + + return $this->getAvatarUrl(); + } + + /** + * Checks user authorization to the action. + * Ensures backwards compatibility + * + * @param string $action + * @return bool + * @deprecated 1.5 Use ->authorize() method instead. + */ + public function authorise($action) + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use ->authorize() method instead', E_USER_DEPRECATED); + + return $this->authorize($action) ?? false; + } + + /** + * Implements Countable interface. + * + * @return int + * @deprecated 1.6 Method makes no sense for user account. + */ + #[\ReturnTypeWillChange] + public function count() + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6', E_USER_DEPRECATED); + + return count($this->jsonSerialize()); + } +} diff --git a/system/src/Grav/Common/Flex/Types/Users/UserCollection.php b/system/src/Grav/Common/Flex/Types/Users/UserCollection.php new file mode 100644 index 0000000..98ecb4c --- /dev/null +++ b/system/src/Grav/Common/Flex/Types/Users/UserCollection.php @@ -0,0 +1,135 @@ + + */ +class UserCollection extends FlexCollection implements UserCollectionInterface +{ + /** + * @return array + */ + public static function getCachedMethods(): array + { + return [ + 'authorize' => 'session', + ] + parent::getCachedMethods(); + } + + /** + * Load user account. + * + * Always creates user object. To check if user exists, use $this->exists(). + * + * @param string $username + * @return UserObject + */ + public function load($username): UserInterface + { + $username = (string)$username; + + if ($username !== '') { + $key = $this->filterUsername($username); + $user = $this->get($key); + if ($user) { + return $user; + } + } else { + $key = ''; + } + + $directory = $this->getFlexDirectory(); + + /** @var UserObject $object */ + $object = $directory->createObject( + [ + 'username' => $username, + 'state' => 'enabled' + ], + $key + ); + + return $object; + } + + /** + * Find a user by username, email, etc + * + * @param string $query the query to search for + * @param string|string[] $fields the fields to search + * @return UserObject + */ + public function find($query, $fields = ['username', 'email']): UserInterface + { + if (is_string($query) && $query !== '') { + foreach ((array)$fields as $field) { + if ($field === 'key') { + $user = $this->get($query); + } elseif ($field === 'storage_key') { + $user = $this->withKeyField('storage_key')->get($query); + } elseif ($field === 'flex_key') { + $user = $this->withKeyField('flex_key')->get($query); + } elseif ($field === 'username') { + $user = $this->get($this->filterUsername($query)); + } else { + $user = parent::find($query, $field); + } + if ($user instanceof UserObject) { + return $user; + } + } + } + + return $this->load(''); + } + + /** + * Delete user account. + * + * @param string $username + * @return bool True if user account was found and was deleted. + */ + public function delete($username): bool + { + $user = $this->load($username); + + $exists = $user->exists(); + if ($exists) { + $user->delete(); + } + + return $exists; + } + + /** + * @param string $key + * @return string + */ + protected function filterUsername(string $key): string + { + $storage = $this->getFlexDirectory()->getStorage(); + if (method_exists($storage, 'normalizeKey')) { + return $storage->normalizeKey($key); + } + + return mb_strtolower($key); + } +} diff --git a/system/src/Grav/Common/Flex/Types/Users/UserIndex.php b/system/src/Grav/Common/Flex/Types/Users/UserIndex.php new file mode 100644 index 0000000..53467b5 --- /dev/null +++ b/system/src/Grav/Common/Flex/Types/Users/UserIndex.php @@ -0,0 +1,206 @@ + + */ +class UserIndex extends FlexIndex implements UserCollectionInterface +{ + public const VERSION = parent::VERSION . '.2'; + + /** + * @param FlexStorageInterface $storage + * @return array + */ + public static function loadEntriesFromStorage(FlexStorageInterface $storage): array + { + // Load saved index. + $index = static::loadIndex($storage); + + $version = $index['version'] ?? 0; + $force = static::VERSION !== $version; + + // TODO: Following check flex index to be out of sync after some saves, disabled until better solution is found. + //$timestamp = $index['timestamp'] ?? 0; + //if (!$force && $timestamp && $timestamp > time() - 1) { + // return $index['index']; + //} + + // Load up-to-date index. + $entries = parent::loadEntriesFromStorage($storage); + + return static::updateIndexFile($storage, $index['index'], $entries, ['force_update' => $force]); + } + + /** + * @param array $meta + * @param array $data + * @param FlexStorageInterface $storage + * @return void + */ + public static function updateObjectMeta(array &$meta, array $data, FlexStorageInterface $storage): void + { + // Username can also be number and stored as such. + $key = (string)($data['username'] ?? $meta['key'] ?? $meta['storage_key']); + $meta['key'] = static::filterUsername($key, $storage); + $meta['email'] = isset($data['email']) ? mb_strtolower($data['email']) : null; + } + + /** + * Load user account. + * + * Always creates user object. To check if user exists, use $this->exists(). + * + * @param string $username + * @return UserObject + */ + public function load($username): UserInterface + { + $username = (string)$username; + + if ($username !== '') { + $key = static::filterUsername($username, $this->getFlexDirectory()->getStorage()); + $user = $this->get($key); + if ($user) { + return $user; + } + } else { + $key = ''; + } + + $directory = $this->getFlexDirectory(); + + /** @var UserObject $object */ + $object = $directory->createObject( + [ + 'username' => $username, + 'state' => 'enabled' + ], + $key + ); + + return $object; + } + + /** + * Delete user account. + * + * @param string $username + * @return bool True if user account was found and was deleted. + */ + public function delete($username): bool + { + $user = $this->load($username); + + $exists = $user->exists(); + if ($exists) { + $user->delete(); + } + + return $exists; + } + + /** + * Find a user by username, email, etc + * + * @param string $query the query to search for + * @param array $fields the fields to search + * @return UserObject + */ + public function find($query, $fields = ['username', 'email']): UserInterface + { + if (is_string($query) && $query !== '') { + foreach ((array)$fields as $field) { + if ($field === 'key') { + $user = $this->get($query); + } elseif ($field === 'storage_key') { + $user = $this->withKeyField('storage_key')->get($query); + } elseif ($field === 'flex_key') { + $user = $this->withKeyField('flex_key')->get($query); + } elseif ($field === 'email') { + $email = mb_strtolower($query); + $user = $this->withKeyField('email')->get($email); + } elseif ($field === 'username') { + $username = static::filterUsername($query, $this->getFlexDirectory()->getStorage()); + $user = $this->get($username); + } else { + $user = $this->__call('find', [$query, $field]); + } + if ($user) { + return $user; + } + } + } + + return $this->load(''); + } + + /** + * @param string $key + * @param FlexStorageInterface $storage + * @return string + */ + protected static function filterUsername(string $key, FlexStorageInterface $storage): string + { + return method_exists($storage, 'normalizeKey') ? $storage->normalizeKey($key) : $key; + } + + /** + * @param FlexStorageInterface $storage + * @return CompiledYamlFile|null + */ + protected static function getIndexFile(FlexStorageInterface $storage) + { + // Load saved index file. + $grav = Grav::instance(); + $locator = $grav['locator']; + $filename = $locator->findResource('user-data://flex/indexes/accounts.yaml', true, true); + + return CompiledYamlFile::instance($filename); + } + + /** + * @param array $entries + * @param array $added + * @param array $updated + * @param array $removed + */ + protected static function onChanges(array $entries, array $added, array $updated, array $removed): void + { + $message = sprintf('Flex: User index updated, %d objects (%d added, %d updated, %d removed).', count($entries), count($added), count($updated), count($removed)); + + $grav = Grav::instance(); + + /** @var Logger $logger */ + $logger = $grav['log']; + $logger->addDebug($message); + + /** @var Debugger $debugger */ + $debugger = $grav['debugger']; + $debugger->addMessage($message, 'debug'); + } +} diff --git a/system/src/Grav/Common/Flex/Types/Users/UserObject.php b/system/src/Grav/Common/Flex/Types/Users/UserObject.php new file mode 100644 index 0000000..5e43b59 --- /dev/null +++ b/system/src/Grav/Common/Flex/Types/Users/UserObject.php @@ -0,0 +1,1059 @@ + 'session', + 'load' => false, + 'find' => false, + 'remove' => false, + 'get' => true, + 'set' => false, + 'undef' => false, + 'def' => false, + ] + parent::getCachedMethods(); + } + + /** + * UserObject constructor. + * @param array $elements + * @param string $key + * @param FlexDirectory $directory + * @param bool $validate + */ + public function __construct(array $elements, $key, FlexDirectory $directory, bool $validate = false) + { + // User can only be authenticated via login. + unset($elements['authenticated'], $elements['authorized']); + + // Define username if it's not set. + if (!isset($elements['username'])) { + $storageKey = $elements['__META']['storage_key'] ?? null; + $storage = $directory->getStorage(); + if (null !== $storageKey && method_exists($storage, 'normalizeKey') && $key === $storage->normalizeKey($storageKey)) { + $elements['username'] = $storageKey; + } else { + $elements['username'] = $key; + } + } + + // Define state if it isn't set. + if (!isset($elements['state'])) { + $elements['state'] = 'enabled'; + } + + parent::__construct($elements, $key, $directory, $validate); + } + + public function __clone() + { + $this->_access = null; + $this->_groups = null; + + parent::__clone(); + } + + /** + * @return void + */ + public function onPrepareRegistration(): void + { + if (!$this->getProperty('access')) { + /** @var Config $config */ + $config = Grav::instance()['config']; + + $groups = $config->get('plugins.login.user_registration.groups', ''); + $access = $config->get('plugins.login.user_registration.access', ['site' => ['login' => true]]); + + $this->setProperty('groups', $groups); + $this->setProperty('access', $access); + } + } + + /** + * Helper to get content editor will fall back if not set + * + * @return string + */ + public function getContentEditor(): string + { + return $this->getProperty('content_editor', 'default'); + } + + /** + * Get value by using dot notation for nested arrays/objects. + * + * @example $value = $this->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|null $separator Separator, defaults to '.' + * @return mixed Value. + */ + public function get($name, $default = null, $separator = null) + { + return $this->getNestedProperty($name, $default, $separator); + } + + /** + * Set value by using dot notation for nested arrays/objects. + * + * @example $data->set('this.is.my.nested.variable', $value); + * + * @param string $name Dot separated path to the requested value. + * @param mixed $value New value. + * @param string|null $separator Separator, defaults to '.' + * @return $this + */ + public function set($name, $value, $separator = null) + { + $this->setNestedProperty($name, $value, $separator); + + return $this; + } + + /** + * Unset value by using dot notation for nested arrays/objects. + * + * @example $data->undef('this.is.my.nested.variable'); + * + * @param string $name Dot separated path to the requested value. + * @param string|null $separator Separator, defaults to '.' + * @return $this + */ + public function undef($name, $separator = null) + { + $this->unsetNestedProperty($name, $separator); + + return $this; + } + + /** + * 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|null $separator Separator, defaults to '.' + * @return $this + */ + public function def($name, $default = null, $separator = null) + { + $this->defNestedProperty($name, $default, $separator); + + return $this; + } + + /** + * @param UserInterface|null $user + * @return bool + */ + public function isMyself(?UserInterface $user = null): bool + { + if (null === $user) { + $user = $this->getActiveUser(); + if ($user && !$user->authenticated) { + $user = null; + } + } + + return $user && $this->username === $user->username; + } + + /** + * Checks user authorization to the action. + * + * @param string $action + * @param string|null $scope + * @return bool|null + */ + public function authorize(string $action, string $scope = null): ?bool + { + if ($scope === 'test') { + // Special scope to test user permissions. + $scope = null; + } else { + // User needs to be enabled. + if ($this->getProperty('state') !== 'enabled') { + return false; + } + + // User needs to be logged in. + if (!$this->getProperty('authenticated')) { + return false; + } + + if (strpos($action, 'login') === false && !$this->getProperty('authorized')) { + // User needs to be authorized (2FA). + return false; + } + + // Workaround bug in Login::isUserAuthorizedForPage() <= Login v3.0.4 + if ((string)(int)$action === $action) { + return false; + } + } + + // Check custom application access. + $authorizeCallable = static::$authorizeCallable; + if ($authorizeCallable instanceof Closure) { + $callable = $authorizeCallable->bindTo($this, $this); + $authorized = $callable($action, $scope); + if (is_bool($authorized)) { + return $authorized; + } + } + + // Check user access. + $access = $this->getAccess(); + $authorized = $access->authorize($action, $scope); + if (is_bool($authorized)) { + return $authorized; + } + + // Check group access. + $authorized = $this->getGroups()->authorize($action, $scope); + if (is_bool($authorized)) { + return $authorized; + } + + // If any specific rule isn't hit, check if user is a superuser. + return $access->authorize('admin.super') === true; + } + + /** + * @param string $property + * @param mixed $default + * @return mixed + */ + public function getProperty($property, $default = null) + { + $value = parent::getProperty($property, $default); + + if ($property === 'avatar') { + $settings = $this->getMediaFieldSettings($property); + $value = $this->parseFileProperty($value, $settings); + } + + return $value; + } + + /** + * @return UserGroupIndex + */ + public function getRoles(): UserGroupIndex + { + return $this->getGroups(); + } + + /** + * Convert object into an array. + * + * @return array + */ + public function toArray() + { + $array = $this->jsonSerialize(); + + $settings = $this->getMediaFieldSettings('avatar'); + $array['avatar'] = $this->parseFileProperty($array['avatar'] ?? null, $settings); + + return $array; + } + + /** + * Convert object into YAML string. + * + * @param int $inline The level where you switch to inline YAML. + * @param int $indent The amount of spaces to use for indentation of nested nodes. + * @return string A YAML string representing the object. + */ + public function toYaml($inline = 5, $indent = 2) + { + $yaml = new YamlFormatter(['inline' => $inline, 'indent' => $indent]); + + return $yaml->encode($this->toArray()); + } + + /** + * Convert object into JSON string. + * + * @return string + */ + public function toJson() + { + $json = new JsonFormatter(); + + return $json->encode($this->toArray()); + } + + /** + * Join nested values together by using blueprints. + * + * @param string $name Dot separated path to the requested value. + * @param mixed $value Value to be joined. + * @param string|null $separator Separator, defaults to '.' + * @return $this + * @throws RuntimeException + */ + public function join($name, $value, $separator = null) + { + $separator = $separator ?? '.'; + $old = $this->get($name, null, $separator); + if ($old !== null) { + if (!is_array($old)) { + throw new RuntimeException('Value ' . $old); + } + + if (is_object($value)) { + $value = (array) $value; + } elseif (!is_array($value)) { + throw new RuntimeException('Value ' . $value); + } + + $value = $this->getBlueprint()->mergeData($old, $value, $name, $separator); + } + + $this->set($name, $value, $separator); + + return $this; + } + + /** + * Get nested structure containing default values defined in the blueprints. + * + * Fields without default value are ignored in the list. + + * @return array + */ + public function getDefaults() + { + return $this->getBlueprint()->getDefaults(); + } + + /** + * Set default values by using blueprints. + * + * @param string $name Dot separated path to the requested value. + * @param mixed $value Value to be joined. + * @param string|null $separator Separator, defaults to '.' + * @return $this + */ + public function joinDefaults($name, $value, $separator = null) + { + if (is_object($value)) { + $value = (array) $value; + } + + $old = $this->get($name, null, $separator); + if ($old !== null) { + $value = $this->getBlueprint()->mergeData($value, $old, $name, $separator ?? '.'); + } + + $this->setNestedProperty($name, $value, $separator); + + return $this; + } + + /** + * Get value from the configuration and join it with given data. + * + * @param string $name Dot separated path to the requested value. + * @param array|object $value Value to be joined. + * @param string $separator Separator, defaults to '.' + * @return array + * @throws RuntimeException + */ + public function getJoined($name, $value, $separator = null) + { + if (is_object($value)) { + $value = (array) $value; + } elseif (!is_array($value)) { + throw new RuntimeException('Value ' . $value); + } + + $old = $this->get($name, null, $separator); + + if ($old === null) { + // No value set; no need to join data. + return $value; + } + + if (!is_array($old)) { + throw new RuntimeException('Value ' . $old); + } + + // Return joined data. + return $this->getBlueprint()->mergeData($old, $value, $name, $separator ?? '.'); + } + + /** + * Set default values to the configuration if variables were not set. + * + * @param array $data + * @return $this + */ + public function setDefaults(array $data) + { + $this->setElements($this->getBlueprint()->mergeData($data, $this->toArray())); + + return $this; + } + + /** + * Validate by blueprints. + * + * @return $this + * @throws \Exception + */ + public function validate() + { + $this->getBlueprint()->validate($this->toArray()); + + return $this; + } + + /** + * Filter all items by using blueprints. + * @return $this + */ + public function filter() + { + $this->setElements($this->getBlueprint()->filter($this->toArray())); + + return $this; + } + + /** + * Get extra items which haven't been defined in blueprints. + * + * @return array + */ + public function extra() + { + return $this->getBlueprint()->extra($this->toArray()); + } + + /** + * Return unmodified data as raw string. + * + * NOTE: This function only returns data which has been saved to the storage. + * + * @return string + */ + public function raw() + { + $file = $this->file(); + + return $file ? $file->raw() : ''; + } + + /** + * Set or get the data storage. + * + * @param FileInterface|null $storage Optionally enter a new storage. + * @return FileInterface|null + */ + public function file(FileInterface $storage = null) + { + if (null !== $storage) { + $this->_storage = $storage; + } + + return $this->_storage; + } + + /** + * @return bool + */ + public function isValid(): bool + { + return $this->getProperty('state') !== null; + } + + /** + * Save user + * + * @return static + */ + public function save() + { + // TODO: We may want to handle this in the storage layer in the future. + $key = $this->getStorageKey(); + if (!$key || strpos($key, '@@')) { + $storage = $this->getFlexDirectory()->getStorage(); + if ($storage instanceof FileStorage) { + $this->setStorageKey($this->getKey()); + } + } + + $password = $this->getProperty('password') ?? $this->getProperty('password1'); + if (null !== $password && '' !== $password) { + $password2 = $this->getProperty('password2'); + if (!\is_string($password) || ($password2 && $password !== $password2)) { + throw new \RuntimeException('Passwords did not match.'); + } + + $this->setProperty('hashed_password', Authentication::create($password)); + } + $this->unsetProperty('password'); + $this->unsetProperty('password1'); + $this->unsetProperty('password2'); + + // Backwards compatibility with older plugins. + $fireEvents = $this->isAdminSite() && $this->getFlexDirectory()->getConfig('object.compat.events', true); + $grav = $this->getContainer(); + if ($fireEvents) { + $self = $this; + $grav->fireEvent('onAdminSave', new Event(['type' => 'flex', 'directory' => $this->getFlexDirectory(), 'object' => &$self])); + if ($self !== $this) { + throw new RuntimeException('Switching Flex User object during onAdminSave event is not supported! Please update plugin.'); + } + } + + $instance = parent::save(); + + // Backwards compatibility with older plugins. + if ($fireEvents) { + $grav->fireEvent('onAdminAfterSave', new Event(['type' => 'flex', 'directory' => $this->getFlexDirectory(), 'object' => $this])); + } + + return $instance; + } + + /** + * @return array + */ + public function prepareStorage(): array + { + $elements = parent::prepareStorage(); + + // Do not save authorization information. + unset($elements['authenticated'], $elements['authorized']); + + return $elements; + } + + /** + * @return MediaCollectionInterface + */ + public function getMedia() + { + /** @var Media $media */ + $media = $this->getFlexMedia(); + + // Deal with shared avatar folder. + $path = $this->getAvatarFile(); + if ($path && !$media[$path] && is_file($path)) { + $medium = MediumFactory::fromFile($path); + if ($medium) { + $media->add($path, $medium); + $name = Utils::basename($path); + if ($name !== $path) { + $media->add($name, $medium); + } + } + } + + return $media; + } + + /** + * @return string|null + */ + public function getMediaFolder(): ?string + { + $folder = $this->getFlexMediaFolder(); + + // Check for shared media + if (!$folder && !$this->getFlexDirectory()->getMediaFolder()) { + $this->_loadMedia = false; + $folder = $this->getBlueprint()->fields()['avatar']['destination'] ?? 'account://avatars'; + } + + return $folder; + } + + /** + * @param string $name + * @return array|object|null + * @internal + */ + public function initRelationship(string $name) + { + switch ($name) { + case 'media': + $list = []; + foreach ($this->getMedia()->all() as $filename => $object) { + $list[] = $this->buildMediaObject(null, $filename, $object); + } + + return $list; + case 'avatar': + return $this->buildMediaObject('avatar', basename($this->getAvatarUrl()), $this->getAvatarImage()); + } + + throw new \InvalidArgumentException(sprintf('%s: Relationship %s does not exist', $this->getFlexType(), $name)); + } + + /** + * @return bool Return true if relationships were updated. + */ + protected function updateRelationships(): bool + { + $modified = $this->getRelationships()->getModified(); + if ($modified) { + foreach ($modified as $relationship) { + $name = $relationship->getName(); + switch ($name) { + case 'avatar': + \assert($relationship instanceof ToOneRelationshipInterface); + $this->updateAvatarRelationship($relationship); + break; + default: + throw new \InvalidArgumentException(sprintf('%s: Relationship %s cannot be modified', $this->getFlexType(), $name), 400); + } + } + + $this->resetRelationships(); + + return true; + } + + return false; + } + + /** + * @param ToOneRelationshipInterface $relationship + */ + protected function updateAvatarRelationship(ToOneRelationshipInterface $relationship): void + { + $files = []; + $avatar = $this->getAvatarImage(); + if ($avatar) { + $files['avatar'][$avatar->filename] = null; + } + + $identifier = $relationship->getIdentifier(); + if ($identifier) { + \assert($identifier instanceof MediaIdentifier); + $object = $identifier->getObject(); + if ($object instanceof UploadedMediaObject) { + $uploadedFile = $object->getUploadedFile(); + if ($uploadedFile) { + $files['avatar'][$uploadedFile->getClientFilename()] = $uploadedFile; + } + } + } + + $this->update([], $files); + } + + /** + * @param string $name + * @return Blueprint + */ + protected function doGetBlueprint(string $name = ''): Blueprint + { + $blueprint = $this->getFlexDirectory()->getBlueprint($name ? '.' . $name : $name); + + // HACK: With folder storage we need to ignore the avatar destination. + if ($this->getFlexDirectory()->getMediaFolder()) { + $field = $blueprint->get('form/fields/avatar'); + if ($field) { + unset($field['destination']); + $blueprint->set('form/fields/avatar', $field); + } + } + + return $blueprint; + } + + /** + * @param UserInterface $user + * @param string $action + * @param string $scope + * @param bool $isMe + * @return bool|null + */ + protected function isAuthorizedOverride(UserInterface $user, string $action, string $scope, bool $isMe = false): ?bool + { + // Check custom application access. + $isAuthorizedCallable = static::$isAuthorizedCallable; + if ($isAuthorizedCallable instanceof Closure) { + $callable = $isAuthorizedCallable->bindTo($this, $this); + $authorized = $callable($user, $action, $scope, $isMe); + if (is_bool($authorized)) { + return $authorized; + } + } + + if ($user instanceof self && $user->getStorageKey() === $this->getStorageKey()) { + // User cannot delete his own account, otherwise he has full access. + return $action !== 'delete'; + } + + return parent::isAuthorizedOverride($user, $action, $scope, $isMe); + } + + /** + * @return string|null + */ + protected function getAvatarFile(): ?string + { + $avatars = $this->getElement('avatar'); + if (is_array($avatars) && $avatars) { + $avatar = array_shift($avatars); + + return $avatar['path'] ?? null; + } + + return null; + } + + /** + * Gets the associated media collection (original images). + * + * @return MediaCollectionInterface Representation of associated media. + */ + protected function getOriginalMedia() + { + $folder = $this->getMediaFolder(); + if ($folder) { + $folder .= '/original'; + } + + return (new Media($folder ?? '', $this->getMediaOrder()))->setTimestamps(); + } + + /** + * @param array $files + * @return void + */ + protected function setUpdatedMedia(array $files): void + { + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + + $media = $this->getMedia(); + if (!$media instanceof MediaUploadInterface) { + return; + } + + $filesystem = Filesystem::getInstance(false); + + $list = []; + $list_original = []; + foreach ($files as $field => $group) { + // Ignore files without a field. + if ($field === '') { + continue; + } + $field = (string)$field; + + // Load settings for the field. + $settings = $this->getMediaFieldSettings($field); + foreach ($group as $filename => $file) { + if ($file) { + // File upload. + $filename = $file->getClientFilename(); + + /** @var FormFlashFile $file */ + $data = $file->jsonSerialize(); + unset($data['tmp_name'], $data['path']); + } else { + // File delete. + $data = null; + } + + if ($file) { + // Check file upload against media limits (except for max size). + $filename = $media->checkUploadedFile($file, $filename, ['filesize' => 0] + $settings); + } + + $self = $settings['self']; + if ($this->_loadMedia && $self) { + $filepath = $filename; + } else { + $filepath = "{$settings['destination']}/{$filename}"; + + // For backwards compatibility we are always using relative path from the installation root. + if ($locator->isStream($filepath)) { + $filepath = $locator->findResource($filepath, false, true); + } + } + + // Special handling for original images. + if (strpos($field, '/original')) { + if ($this->_loadMedia && $self) { + $list_original[$filename] = [$file, $settings]; + } + continue; + } + + // Calculate path without the retina scaling factor. + $realpath = $filesystem->pathname($filepath) . str_replace(['@3x', '@2x'], '', Utils::basename($filepath)); + + $list[$filename] = [$file, $settings]; + + $path = str_replace('.', "\n", $field); + if (null !== $data) { + $data['name'] = $filename; + $data['path'] = $filepath; + + $this->setNestedProperty("{$path}\n{$realpath}", $data, "\n"); + } else { + $this->unsetNestedProperty("{$path}\n{$realpath}", "\n"); + } + } + } + + $this->clearMediaCache(); + + $this->_uploads = $list; + $this->_uploads_original = $list_original; + } + + protected function saveUpdatedMedia(): void + { + $media = $this->getMedia(); + if (!$media instanceof MediaUploadInterface) { + throw new RuntimeException('Internal error UO101'); + } + + // Upload/delete original sized images. + /** + * @var string $filename + * @var UploadedFileInterface|array|null $file + */ + foreach ($this->_uploads_original ?? [] as $filename => $file) { + $filename = 'original/' . $filename; + if (is_array($file)) { + [$file, $settings] = $file; + } else { + $settings = null; + } + if ($file instanceof UploadedFileInterface) { + $media->copyUploadedFile($file, $filename, $settings); + } else { + $media->deleteFile($filename, $settings); + } + } + + // Upload/delete altered files. + /** + * @var string $filename + * @var UploadedFileInterface|array|null $file + */ + foreach ($this->getUpdatedMedia() as $filename => $file) { + if (is_array($file)) { + [$file, $settings] = $file; + } else { + $settings = null; + } + if ($file instanceof UploadedFileInterface) { + $media->copyUploadedFile($file, $filename, $settings); + } else { + $media->deleteFile($filename, $settings); + } + } + + $this->setUpdatedMedia([]); + $this->clearMediaCache(); + } + + /** + * @return array + */ + protected function doSerialize(): array + { + return [ + 'type' => $this->getFlexType(), + 'key' => $this->getKey(), + 'elements' => $this->jsonSerialize(), + 'storage' => $this->getMetaData() + ]; + } + + /** + * @return UserGroupIndex + */ + protected function getUserGroups() + { + $grav = Grav::instance(); + + /** @var Flex $flex */ + $flex = $grav['flex']; + + /** @var UserGroupCollection|null $groups */ + $groups = $flex->getDirectory('user-groups'); + if ($groups) { + /** @var UserGroupIndex $index */ + $index = $groups->getIndex(); + + return $index; + } + + return $grav['user_groups']; + } + + /** + * @return UserGroupIndex + */ + protected function getGroups() + { + if (null === $this->_groups) { + /** @var UserGroupIndex $groups */ + $groups = $this->getUserGroups()->select((array)$this->getProperty('groups')); + $this->_groups = $groups; + } + + return $this->_groups; + } + + /** + * @return Access + */ + protected function getAccess(): Access + { + if (null === $this->_access) { + $this->_access = new Access($this->getProperty('access')); + } + + return $this->_access; + } + + /** + * @param mixed $value + * @return array + */ + protected function offsetLoad_access($value): array + { + if (!$value instanceof Access) { + $value = new Access($value); + } + + return $value->jsonSerialize(); + } + + /** + * @param mixed $value + * @return array + */ + protected function offsetPrepare_access($value): array + { + return $this->offsetLoad_access($value); + } + + /** + * @param array|null $value + * @return array|null + */ + protected function offsetSerialize_access(?array $value): ?array + { + return $value; + } +} diff --git a/system/src/Grav/Common/Form/FormFlash.php b/system/src/Grav/Common/Form/FormFlash.php new file mode 100644 index 0000000..4f624ea --- /dev/null +++ b/system/src/Grav/Common/Form/FormFlash.php @@ -0,0 +1,107 @@ +files as $field => $files) { + if (strpos($field, '/')) { + continue; + } + foreach ($files as $file) { + if (is_array($file)) { + $file['tmp_name'] = $this->getTmpDir() . '/' . $file['tmp_name']; + $fields[$field][$file['path'] ?? $file['name']] = $file; + } + } + } + + return $fields; + } + + /** + * @param string $field + * @param string $filename + * @param array $upload + * @return bool + * @deprecated 1.6 For backwards compatibility only, do not use + */ + public function uploadFile(string $field, string $filename, array $upload): bool + { + if (!$this->uniqueId) { + return false; + } + + $tmp_dir = $this->getTmpDir(); + Folder::create($tmp_dir); + + $tmp_file = $upload['file']['tmp_name']; + $basename = Utils::basename($tmp_file); + + if (!move_uploaded_file($tmp_file, $tmp_dir . '/' . $basename)) { + return false; + } + + $upload['file']['tmp_name'] = $basename; + $upload['file']['name'] = $filename; + + $this->addFileInternal($field, $filename, $upload['file']); + + return true; + } + + /** + * @param string $field + * @param string $filename + * @param array $upload + * @param array $crop + * @return bool + * @deprecated 1.6 For backwards compatibility only, do not use + */ + public function cropFile(string $field, string $filename, array $upload, array $crop): bool + { + if (!$this->uniqueId) { + return false; + } + + $tmp_dir = $this->getTmpDir(); + Folder::create($tmp_dir); + + $tmp_file = $upload['file']['tmp_name']; + $basename = Utils::basename($tmp_file); + + if (!move_uploaded_file($tmp_file, $tmp_dir . '/' . $basename)) { + return false; + } + + $upload['file']['tmp_name'] = $basename; + $upload['file']['name'] = $filename; + + $this->addFileInternal($field, $filename, $upload['file'], $crop); + + return true; + } +} diff --git a/system/src/Grav/Common/GPM/AbstractCollection.php b/system/src/Grav/Common/GPM/AbstractCollection.php new file mode 100644 index 0000000..a4d11f4 --- /dev/null +++ b/system/src/Grav/Common/GPM/AbstractCollection.php @@ -0,0 +1,41 @@ +toArray(), JSON_THROW_ON_ERROR); + } + + /** + * @return array + */ + public function toArray() + { + $items = []; + + foreach ($this->items as $name => $package) { + $items[$name] = $package->toArray(); + } + + return $items; + } +} diff --git a/system/src/Grav/Common/GPM/Common/AbstractPackageCollection.php b/system/src/Grav/Common/GPM/Common/AbstractPackageCollection.php new file mode 100644 index 0000000..121a48a --- /dev/null +++ b/system/src/Grav/Common/GPM/Common/AbstractPackageCollection.php @@ -0,0 +1,50 @@ +items as $name => $package) { + $items[$name] = $package->toArray(); + } + + return json_encode($items, JSON_THROW_ON_ERROR); + } + + /** + * @return array + */ + public function toArray() + { + $items = []; + + foreach ($this->items as $name => $package) { + $items[$name] = $package->toArray(); + } + + return $items; + } +} diff --git a/system/src/Grav/Common/GPM/Common/CachedCollection.php b/system/src/Grav/Common/GPM/Common/CachedCollection.php new file mode 100644 index 0000000..7613c76 --- /dev/null +++ b/system/src/Grav/Common/GPM/Common/CachedCollection.php @@ -0,0 +1,43 @@ + $item) { + $this->append([$name => $item]); + } + } +} diff --git a/system/src/Grav/Common/GPM/Common/Package.php b/system/src/Grav/Common/GPM/Common/Package.php new file mode 100644 index 0000000..07ecc5b --- /dev/null +++ b/system/src/Grav/Common/GPM/Common/Package.php @@ -0,0 +1,99 @@ +data = $package; + + if ($type) { + $this->data->set('package_type', $type); + } + } + + /** + * @return Data + */ + public function getData() + { + return $this->data; + } + + /** + * @param string $key + * @return mixed + */ + #[\ReturnTypeWillChange] + public function __get($key) + { + return $this->data->get($key); + } + + /** + * @param string $key + * @param mixed $value + * @return void + */ + #[\ReturnTypeWillChange] + public function __set($key, $value) + { + $this->data->set($key, $value); + } + + /** + * @param string $key + * @return bool + */ + #[\ReturnTypeWillChange] + public function __isset($key) + { + return isset($this->data->{$key}); + } + + /** + * @return string + */ + #[\ReturnTypeWillChange] + public function __toString() + { + return $this->toJson(); + } + + /** + * @return string + */ + public function toJson() + { + return $this->data->toJson(); + } + + /** + * @return array + */ + public function toArray() + { + return $this->data->toArray(); + } +} diff --git a/system/src/Grav/Common/GPM/GPM.php b/system/src/Grav/Common/GPM/GPM.php new file mode 100644 index 0000000..d4690c2 --- /dev/null +++ b/system/src/Grav/Common/GPM/GPM.php @@ -0,0 +1,1270 @@ + 'user/plugins/%name%', + 'themes' => 'user/themes/%name%', + 'skeletons' => 'user/' + ]; + + /** + * Creates a new GPM instance with Local and Remote packages available + * + * @param bool $refresh Applies to Remote Packages only and forces a refetch of data + * @param callable|null $callback Either a function or callback in array notation + */ + public function __construct($refresh = false, $callback = null) + { + parent::__construct(); + + Folder::create(CACHE_DIR . '/gpm'); + + $this->cache = []; + $this->installed = new Local\Packages(); + $this->refresh = $refresh; + $this->callback = $callback; + } + + /** + * Magic getter method + * + * @param string $offset Asset name value + * @return mixed Asset value + */ + #[\ReturnTypeWillChange] + public function __get($offset) + { + switch ($offset) { + case 'grav': + return $this->getGrav(); + } + + return parent::__get($offset); + } + + /** + * Magic method to determine if the attribute is set + * + * @param string $offset Asset name value + * @return bool True if the value is set + */ + #[\ReturnTypeWillChange] + public function __isset($offset) + { + switch ($offset) { + case 'grav': + return $this->getGrav() !== null; + } + + return parent::__isset($offset); + } + + /** + * Return the locally installed packages + * + * @return Local\Packages + */ + public function getInstalled() + { + return $this->installed; + } + + /** + * Returns the Locally installable packages + * + * @param array $list_type_installed + * @return array The installed packages + */ + public function getInstallable($list_type_installed = ['plugins' => true, 'themes' => true]) + { + $items = ['total' => 0]; + foreach ($list_type_installed as $type => $type_installed) { + if ($type_installed === false) { + continue; + } + $methodInstallableType = 'getInstalled' . ucfirst($type); + $to_install = $this->$methodInstallableType(); + $items[$type] = $to_install; + $items['total'] += count($to_install); + } + + return $items; + } + + /** + * Returns the amount of locally installed packages + * + * @return int Amount of installed packages + */ + public function countInstalled() + { + $installed = $this->getInstalled(); + + return count($installed['plugins']) + count($installed['themes']); + } + + /** + * Return the instance of a specific Package + * + * @param string $slug The slug of the Package + * @return Local\Package|null The instance of the Package + */ + public function getInstalledPackage($slug) + { + return $this->getInstalledPlugin($slug) ?? $this->getInstalledTheme($slug); + } + + /** + * Return the instance of a specific Plugin + * + * @param string $slug The slug of the Plugin + * @return Local\Package|null The instance of the Plugin + */ + public function getInstalledPlugin($slug) + { + return $this->installed['plugins'][$slug] ?? null; + } + + /** + * Returns the Locally installed plugins + * @return Iterator The installed plugins + */ + public function getInstalledPlugins() + { + return $this->installed['plugins']; + } + + + /** + * Returns the plugin's enabled state + * + * @param string $slug + * @return bool True if the Plugin is Enabled. False if manually set to enable:false. Null otherwise. + */ + public function isPluginEnabled($slug): bool + { + $grav = Grav::instance(); + + return ($grav['config']['plugins'][$slug]['enabled'] ?? false) === true; + } + + /** + * Checks if a Plugin is installed + * + * @param string $slug The slug of the Plugin + * @return bool True if the Plugin has been installed. False otherwise + */ + public function isPluginInstalled($slug): bool + { + return isset($this->installed['plugins'][$slug]); + } + + /** + * @param string $slug + * @return bool + */ + public function isPluginInstalledAsSymlink($slug) + { + $plugin = $this->getInstalledPlugin($slug); + + return (bool)($plugin->symlink ?? false); + } + + /** + * Return the instance of a specific Theme + * + * @param string $slug The slug of the Theme + * @return Local\Package|null The instance of the Theme + */ + public function getInstalledTheme($slug) + { + return $this->installed['themes'][$slug] ?? null; + } + + /** + * Returns the Locally installed themes + * + * @return Iterator The installed themes + */ + public function getInstalledThemes() + { + return $this->installed['themes']; + } + + /** + * Checks if a Theme is enabled + * + * @param string $slug The slug of the Theme + * @return bool True if the Theme has been set to the default theme. False if installed, but not enabled. Null otherwise. + */ + public function isThemeEnabled($slug): bool + { + $grav = Grav::instance(); + + $current_theme = $grav['config']['system']['pages']['theme'] ?? null; + + return $current_theme === $slug; + } + + /** + * Checks if a Theme is installed + * + * @param string $slug The slug of the Theme + * @return bool True if the Theme has been installed. False otherwise + */ + public function isThemeInstalled($slug): bool + { + return isset($this->installed['themes'][$slug]); + } + + /** + * Returns the amount of updates available + * + * @return int Amount of available updates + */ + public function countUpdates() + { + return count($this->getUpdatablePlugins()) + count($this->getUpdatableThemes()); + } + + /** + * Returns an array of Plugins and Themes that can be updated. + * Plugins and Themes are extended with the `available` property that relies to the remote version + * + * @param array $list_type_update specifies what type of package to update + * @return array Array of updatable Plugins and Themes. + * Format: ['total' => int, 'plugins' => array, 'themes' => array] + */ + public function getUpdatable($list_type_update = ['plugins' => true, 'themes' => true]) + { + $items = ['total' => 0]; + foreach ($list_type_update as $type => $type_updatable) { + if ($type_updatable === false) { + continue; + } + $methodUpdatableType = 'getUpdatable' . ucfirst($type); + $to_update = $this->$methodUpdatableType(); + $items[$type] = $to_update; + $items['total'] += count($to_update); + } + + return $items; + } + + /** + * Returns an array of Plugins that can be updated. + * The Plugins are extended with the `available` property that relies to the remote version + * + * @return array Array of updatable Plugins + */ + public function getUpdatablePlugins() + { + $items = []; + + $repository = $this->getRepository(); + if (null === $repository) { + return $items; + } + + $plugins = $repository['plugins']; + + // local cache to speed things up + if (isset($this->cache[__METHOD__])) { + return $this->cache[__METHOD__]; + } + + foreach ($this->installed['plugins'] as $slug => $plugin) { + if (!isset($plugins[$slug]) || $plugin->symlink || !$plugin->version || $plugin->gpm === false) { + continue; + } + + $local_version = $plugin->version ?? 'Unknown'; + $remote_version = $plugins[$slug]->version; + + if (version_compare($local_version, $remote_version) < 0) { + $plugins[$slug]->available = $remote_version; + $plugins[$slug]->version = $local_version; + $plugins[$slug]->type = $plugins[$slug]->release_type; + $items[$slug] = $plugins[$slug]; + } + } + + $this->cache[__METHOD__] = $items; + + return $items; + } + + /** + * Get the latest release of a package from the GPM + * + * @param string $package_name + * @return string|null + */ + public function getLatestVersionOfPackage($package_name) + { + $repository = $this->getRepository(); + if (null === $repository) { + return null; + } + + $plugins = $repository['plugins']; + if (isset($plugins[$package_name])) { + return $plugins[$package_name]->available ?: $plugins[$package_name]->version; + } + + //Not a plugin, it's a theme? + $themes = $repository['themes']; + if (isset($themes[$package_name])) { + return $themes[$package_name]->available ?: $themes[$package_name]->version; + } + + return null; + } + + /** + * Check if a Plugin or Theme is updatable + * + * @param string $slug The slug of the package + * @return bool True if updatable. False otherwise or if not found + */ + public function isUpdatable($slug) + { + return $this->isPluginUpdatable($slug) || $this->isThemeUpdatable($slug); + } + + /** + * Checks if a Plugin is updatable + * + * @param string $plugin The slug of the Plugin + * @return bool True if the Plugin is updatable. False otherwise + */ + public function isPluginUpdatable($plugin) + { + return array_key_exists($plugin, (array)$this->getUpdatablePlugins()); + } + + /** + * Returns an array of Themes that can be updated. + * The Themes are extended with the `available` property that relies to the remote version + * + * @return array Array of updatable Themes + */ + public function getUpdatableThemes() + { + $items = []; + + $repository = $this->getRepository(); + if (null === $repository) { + return $items; + } + + $themes = $repository['themes']; + + // local cache to speed things up + if (isset($this->cache[__METHOD__])) { + return $this->cache[__METHOD__]; + } + + foreach ($this->installed['themes'] as $slug => $plugin) { + if (!isset($themes[$slug]) || $plugin->symlink || !$plugin->version || $plugin->gpm === false) { + continue; + } + + $local_version = $plugin->version ?? 'Unknown'; + $remote_version = $themes[$slug]->version; + + if (version_compare($local_version, $remote_version) < 0) { + $themes[$slug]->available = $remote_version; + $themes[$slug]->version = $local_version; + $themes[$slug]->type = $themes[$slug]->release_type; + $items[$slug] = $themes[$slug]; + } + } + + $this->cache[__METHOD__] = $items; + + return $items; + } + + /** + * Checks if a Theme is Updatable + * + * @param string $theme The slug of the Theme + * @return bool True if the Theme is updatable. False otherwise + */ + public function isThemeUpdatable($theme) + { + return array_key_exists($theme, (array)$this->getUpdatableThemes()); + } + + /** + * Get the release type of a package (stable / testing) + * + * @param string $package_name + * @return string|null + */ + public function getReleaseType($package_name) + { + $repository = $this->getRepository(); + if (null === $repository) { + return null; + } + + $plugins = $repository['plugins']; + if (isset($plugins[$package_name])) { + return $plugins[$package_name]->release_type; + } + + //Not a plugin, it's a theme? + $themes = $repository['themes']; + if (isset($themes[$package_name])) { + return $themes[$package_name]->release_type; + } + + return null; + } + + /** + * Returns true if the package latest release is stable + * + * @param string $package_name + * @return bool + */ + public function isStableRelease($package_name) + { + return $this->getReleaseType($package_name) === 'stable'; + } + + /** + * Returns true if the package latest release is testing + * + * @param string $package_name + * @return bool + */ + public function isTestingRelease($package_name) + { + $package = $this->getInstalledPackage($package_name); + $testing = $package->testing ?? false; + + return $this->getReleaseType($package_name) === 'testing' || $testing; + } + + /** + * Returns a Plugin from the repository + * + * @param string $slug The slug of the Plugin + * @return Remote\Package|null Package if found, NULL if not + */ + public function getRepositoryPlugin($slug) + { + $packages = $this->getRepositoryPlugins(); + + return $packages ? ($packages[$slug] ?? null) : null; + } + + /** + * Returns the list of Plugins available in the repository + * + * @return Iterator|null The Plugins remotely available + */ + public function getRepositoryPlugins() + { + return $this->getRepository()['plugins'] ?? null; + } + + /** + * Returns a Theme from the repository + * + * @param string $slug The slug of the Theme + * @return Remote\Package|null Package if found, NULL if not + */ + public function getRepositoryTheme($slug) + { + $packages = $this->getRepositoryThemes(); + + return $packages ? ($packages[$slug] ?? null) : null; + } + + /** + * Returns the list of Themes available in the repository + * + * @return Iterator|null The Themes remotely available + */ + public function getRepositoryThemes() + { + return $this->getRepository()['themes'] ?? null; + } + + /** + * Returns the list of Plugins and Themes available in the repository + * + * @return Remote\Packages|null Available Plugins and Themes + * Format: ['plugins' => array, 'themes' => array] + */ + public function getRepository() + { + if (null === $this->repository) { + try { + $this->repository = new Remote\Packages($this->refresh, $this->callback); + } catch (Exception $e) {} + } + + return $this->repository; + } + + /** + * Returns Grav version available in the repository + * + * @return Remote\GravCore|null + */ + public function getGrav() + { + if (null === $this->grav) { + try { + $this->grav = new Remote\GravCore($this->refresh, $this->callback); + } catch (Exception $e) {} + } + + return $this->grav; + } + + /** + * Searches for a Package in the repository + * + * @param string $search Can be either the slug or the name + * @param bool $ignore_exception True if should not fire an exception (for use in Twig) + * @return Remote\Package|false Package if found, FALSE if not + */ + public function findPackage($search, $ignore_exception = false) + { + $search = strtolower($search); + + $found = $this->getRepositoryPlugin($search) ?? $this->getRepositoryTheme($search); + if ($found) { + return $found; + } + + $themes = $this->getRepositoryThemes(); + $plugins = $this->getRepositoryPlugins(); + + if (null === $themes || null === $plugins) { + if (!is_writable(GRAV_ROOT . '/cache/gpm')) { + throw new RuntimeException('The cache/gpm folder is not writable. Please check the folder permissions.'); + } + + if ($ignore_exception) { + return false; + } + + throw new RuntimeException('GPM not reachable. Please check your internet connection or check the Grav site is reachable'); + } + + foreach ($themes as $slug => $theme) { + if ($search === $slug || $search === $theme->name) { + return $theme; + } + } + + foreach ($plugins as $slug => $plugin) { + if ($search === $slug || $search === $plugin->name) { + return $plugin; + } + } + + return false; + } + + /** + * Download the zip package via the URL + * + * @param string $package_file + * @param string $tmp + * @return string|null + */ + public static function downloadPackage($package_file, $tmp) + { + $package = parse_url($package_file); + if (!is_array($package)) { + throw new \RuntimeException("Malformed GPM URL: {$package_file}"); + } + + $filename = Utils::basename($package['path'] ?? ''); + + if (Grav::instance()['config']->get('system.gpm.official_gpm_only') && ($package['host'] ?? null) !== 'getgrav.org') { + throw new RuntimeException('Only official GPM URLs are allowed. You can modify this behavior in the System configuration.'); + } + + $output = Response::get($package_file, []); + + if ($output) { + Folder::create($tmp); + file_put_contents($tmp . DS . $filename, $output); + return $tmp . DS . $filename; + } + + return null; + } + + /** + * Copy the local zip package to tmp + * + * @param string $package_file + * @param string $tmp + * @return string|null + */ + public static function copyPackage($package_file, $tmp) + { + $package_file = realpath($package_file); + + if ($package_file && file_exists($package_file)) { + $filename = Utils::basename($package_file); + Folder::create($tmp); + copy($package_file, $tmp . DS . $filename); + return $tmp . DS . $filename; + } + + return null; + } + + /** + * Try to guess the package type from the source files + * + * @param string $source + * @return string|false + */ + public static function getPackageType($source) + { + $plugin_regex = '/^class\\s{1,}[a-zA-Z0-9]{1,}\\s{1,}extends.+Plugin/m'; + $theme_regex = '/^class\\s{1,}[a-zA-Z0-9]{1,}\\s{1,}extends.+Theme/m'; + + if (file_exists($source . 'system/defines.php') && + file_exists($source . 'system/config/system.yaml') + ) { + return 'grav'; + } + + // must have a blueprint + if (!file_exists($source . 'blueprints.yaml')) { + return false; + } + + // either theme or plugin + $name = Utils::basename($source); + if (Utils::contains($name, 'theme')) { + return 'theme'; + } + if (Utils::contains($name, 'plugin')) { + return 'plugin'; + } + + $glob = glob($source . '*.php') ?: []; + foreach ($glob as $filename) { + $contents = file_get_contents($filename); + if (!$contents) { + continue; + } + if (preg_match($theme_regex, $contents)) { + return 'theme'; + } + if (preg_match($plugin_regex, $contents)) { + return 'plugin'; + } + } + + // Assume it's a theme + return 'theme'; + } + + /** + * Try to guess the package name from the source files + * + * @param string $source + * @return string|false + */ + public static function getPackageName($source) + { + $ignore_yaml_files = ['blueprints', 'languages']; + + $glob = glob($source . '*.yaml') ?: []; + foreach ($glob as $filename) { + $name = strtolower(Utils::basename($filename, '.yaml')); + if (in_array($name, $ignore_yaml_files)) { + continue; + } + + return $name; + } + + return false; + } + + /** + * Find/Parse the blueprint file + * + * @param string $source + * @return array|false + */ + public static function getBlueprints($source) + { + $blueprint_file = $source . 'blueprints.yaml'; + if (!file_exists($blueprint_file)) { + return false; + } + + $file = YamlFile::instance($blueprint_file); + $blueprint = (array)$file->content(); + $file->free(); + + return $blueprint; + } + + /** + * Get the install path for a name and a particular type of package + * + * @param string $type + * @param string $name + * @return string + */ + public static function getInstallPath($type, $name) + { + $locator = Grav::instance()['locator']; + + if ($type === 'theme') { + $install_path = $locator->findResource('themes://', false) . DS . $name; + } else { + $install_path = $locator->findResource('plugins://', false) . DS . $name; + } + + return $install_path; + } + + /** + * Searches for a list of Packages in the repository + * + * @param array $searches An array of either slugs or names + * @return array Array of found Packages + * Format: ['total' => int, 'not_found' => array, ] + */ + public function findPackages($searches = []) + { + $packages = ['total' => 0, 'not_found' => []]; + $inflector = new Inflector(); + + foreach ($searches as $search) { + $repository = ''; + // if this is an object, get the search data from the key + if (is_object($search)) { + $search = (array)$search; + $key = key($search); + $repository = $search[$key]; + $search = $key; + } + + $found = $this->findPackage($search); + if ($found) { + // set override repository if provided + if ($repository) { + $found->override_repository = $repository; + } + if (!isset($packages[$found->package_type])) { + $packages[$found->package_type] = []; + } + + $packages[$found->package_type][$found->slug] = $found; + $packages['total']++; + } else { + // make a best guess at the type based on the repo URL + if (Utils::contains($repository, '-theme')) { + $type = 'themes'; + } else { + $type = 'plugins'; + } + + $not_found = new stdClass(); + $not_found->name = $inflector::camelize($search); + $not_found->slug = $search; + $not_found->package_type = $type; + $not_found->install_path = str_replace('%name%', $search, $this->install_paths[$type]); + $not_found->override_repository = $repository; + $packages['not_found'][$search] = $not_found; + } + } + + return $packages; + } + + /** + * Return the list of packages that have the passed one as dependency + * + * @param string $slug The slug name of the package + * @return array + */ + public function getPackagesThatDependOnPackage($slug) + { + $plugins = $this->getInstalledPlugins(); + $themes = $this->getInstalledThemes(); + $packages = array_merge($plugins->toArray(), $themes->toArray()); + + $list = []; + foreach ($packages as $package_name => $package) { + $dependencies = $package['dependencies'] ?? []; + foreach ($dependencies as $dependency) { + if (is_array($dependency) && isset($dependency['name'])) { + $dependency = $dependency['name']; + } + + if ($dependency === $slug) { + $list[] = $package_name; + } + } + } + + return $list; + } + + + /** + * Get the required version of a dependency of a package + * + * @param string $package_slug + * @param string $dependency_slug + * @return mixed|null + */ + public function getVersionOfDependencyRequiredByPackage($package_slug, $dependency_slug) + { + $dependencies = $this->getInstalledPackage($package_slug)->dependencies ?? []; + foreach ($dependencies as $dependency) { + if (isset($dependency[$dependency_slug])) { + return $dependency[$dependency_slug]; + } + } + + return null; + } + + /** + * Check the package identified by $slug can be updated to the version passed as argument. + * Thrown an exception if it cannot be updated because another package installed requires it to be at an older version. + * + * @param string $slug + * @param string $version_with_operator + * @param array $ignore_packages_list + * @return bool + * @throws RuntimeException + */ + public function checkNoOtherPackageNeedsThisDependencyInALowerVersion($slug, $version_with_operator, $ignore_packages_list) + { + // check if any of the currently installed package need this in a lower version than the one we need. In case, abort and tell which package + $dependent_packages = $this->getPackagesThatDependOnPackage($slug); + $version = $this->calculateVersionNumberFromDependencyVersion($version_with_operator); + + if (count($dependent_packages)) { + foreach ($dependent_packages as $dependent_package) { + $other_dependency_version_with_operator = $this->getVersionOfDependencyRequiredByPackage($dependent_package, $slug); + $other_dependency_version = $this->calculateVersionNumberFromDependencyVersion($other_dependency_version_with_operator); + + // check version is compatible with the one needed by the current package + if ($this->versionFormatIsNextSignificantRelease($other_dependency_version_with_operator)) { + $compatible = $this->checkNextSignificantReleasesAreCompatible($version, $other_dependency_version); + if (!$compatible && !in_array($dependent_package, $ignore_packages_list, true)) { + throw new RuntimeException( + "Package $slug is required in an older version by package $dependent_package. This package needs a newer version, and because of this it cannot be installed. The $dependent_package package must be updated to use a newer release of $slug.", + 2 + ); + } + } + } + } + + return true; + } + + /** + * Check the passed packages list can be updated + * + * @param array $packages_names_list + * @return void + * @throws Exception + */ + public function checkPackagesCanBeInstalled($packages_names_list) + { + foreach ($packages_names_list as $package_name) { + $latest = $this->getLatestVersionOfPackage($package_name); + $this->checkNoOtherPackageNeedsThisDependencyInALowerVersion($package_name, $latest, $packages_names_list); + } + } + + /** + * Fetch the dependencies, check the installed packages and return an array with + * the list of packages with associated an information on what to do: install, update or ignore. + * + * `ignore` means the package is already installed and can be safely left as-is. + * `install` means the package is not installed and must be installed. + * `update` means the package is already installed and must be updated as a dependency needs a higher version. + * + * @param array $packages + * @return array + * @throws RuntimeException + */ + public function getDependencies($packages) + { + $dependencies = $this->calculateMergedDependenciesOfPackages($packages); + foreach ($dependencies as $dependency_slug => $dependencyVersionWithOperator) { + $dependency_slug = (string)$dependency_slug; + if (in_array($dependency_slug, $packages, true)) { + unset($dependencies[$dependency_slug]); + continue; + } + + // Check PHP version + if ($dependency_slug === 'php') { + $testVersion = $this->calculateVersionNumberFromDependencyVersion($dependencyVersionWithOperator); + if (version_compare($testVersion, PHP_VERSION) === 1) { + //Needs a Grav update first + throw new RuntimeException("One of the packages require PHP {$dependencies['php']}. Please update PHP to resolve this"); + } + + unset($dependencies[$dependency_slug]); + continue; + } + + //First, check for Grav dependency. If a dependency requires Grav > the current version, abort and tell. + if ($dependency_slug === 'grav') { + $testVersion = $this->calculateVersionNumberFromDependencyVersion($dependencyVersionWithOperator); + if (version_compare($testVersion, GRAV_VERSION) === 1) { + //Needs a Grav update first + throw new RuntimeException("One of the packages require Grav {$dependencies['grav']}. Please update Grav to the latest release."); + } + + unset($dependencies[$dependency_slug]); + continue; + } + + if ($this->isPluginInstalled($dependency_slug)) { + if ($this->isPluginInstalledAsSymlink($dependency_slug)) { + unset($dependencies[$dependency_slug]); + continue; + } + + $dependencyVersion = $this->calculateVersionNumberFromDependencyVersion($dependencyVersionWithOperator); + + // get currently installed version + $locator = Grav::instance()['locator']; + $blueprints_path = $locator->findResource('plugins://' . $dependency_slug . DS . 'blueprints.yaml'); + $file = YamlFile::instance($blueprints_path); + $package_yaml = $file->content(); + $file->free(); + $currentlyInstalledVersion = $package_yaml['version']; + + // if requirement is next significant release, check is compatible with currently installed version, might not be + if ($this->versionFormatIsNextSignificantRelease($dependencyVersionWithOperator) + && $this->firstVersionIsLower($dependencyVersion, $currentlyInstalledVersion)) { + $compatible = $this->checkNextSignificantReleasesAreCompatible($dependencyVersion, $currentlyInstalledVersion); + + if (!$compatible) { + throw new RuntimeException( + 'Dependency ' . $dependency_slug . ' is required in an older version than the one installed. This package must be updated. Please get in touch with its developer.', + 2 + ); + } + } + + //if I already have the latest release, remove the dependency + $latestRelease = $this->getLatestVersionOfPackage($dependency_slug); + + if ($this->firstVersionIsLower($latestRelease, $dependencyVersion)) { + //throw an exception if a required version cannot be found in the GPM yet + throw new RuntimeException( + 'Dependency ' . $package_yaml['name'] . ' is required in version ' . $dependencyVersion . ' which is higher than the latest release, ' . $latestRelease . '. Try running `bin/gpm -f index` to force a refresh of the GPM cache', + 1 + ); + } + + if ($this->firstVersionIsLower($currentlyInstalledVersion, $dependencyVersion)) { + $dependencies[$dependency_slug] = 'update'; + } elseif ($currentlyInstalledVersion === $latestRelease) { + unset($dependencies[$dependency_slug]); + } else { + // an update is not strictly required mark as 'ignore' + $dependencies[$dependency_slug] = 'ignore'; + } + } else { + $dependencyVersion = $this->calculateVersionNumberFromDependencyVersion($dependencyVersionWithOperator); + + // if requirement is next significant release, check is compatible with latest available version, might not be + if ($this->versionFormatIsNextSignificantRelease($dependencyVersionWithOperator)) { + $latestVersionOfPackage = $this->getLatestVersionOfPackage($dependency_slug); + if ($this->firstVersionIsLower($dependencyVersion, $latestVersionOfPackage)) { + $compatible = $this->checkNextSignificantReleasesAreCompatible( + $dependencyVersion, + $latestVersionOfPackage + ); + + if (!$compatible) { + throw new RuntimeException( + 'Dependency ' . $dependency_slug . ' is required in an older version than the latest release available, and it cannot be installed. This package must be updated. Please get in touch with its developer.', + 2 + ); + } + } + } + + $dependencies[$dependency_slug] = 'install'; + } + } + + $dependencies_slugs = array_keys($dependencies); + $this->checkNoOtherPackageNeedsTheseDependenciesInALowerVersion(array_merge($packages, $dependencies_slugs)); + + return $dependencies; + } + + /** + * @param array $dependencies_slugs + * @return void + */ + public function checkNoOtherPackageNeedsTheseDependenciesInALowerVersion($dependencies_slugs) + { + foreach ($dependencies_slugs as $dependency_slug) { + $this->checkNoOtherPackageNeedsThisDependencyInALowerVersion( + $dependency_slug, + $this->getLatestVersionOfPackage($dependency_slug), + $dependencies_slugs + ); + } + } + + /** + * @param string $firstVersion + * @param string $secondVersion + * @return bool + */ + private function firstVersionIsLower($firstVersion, $secondVersion) + { + return version_compare($firstVersion, $secondVersion) === -1; + } + + /** + * Calculates and merges the dependencies of a package + * + * @param string $packageName The package information + * @param array $dependencies The dependencies array + * @return array + */ + private function calculateMergedDependenciesOfPackage($packageName, $dependencies) + { + $packageData = $this->findPackage($packageName); + + if (empty($packageData->dependencies)) { + return $dependencies; + } + + foreach ($packageData->dependencies as $dependency) { + $dependencyName = $dependency['name'] ?? null; + if (!$dependencyName) { + continue; + } + + $dependencyVersion = $dependency['version'] ?? '*'; + + if (!isset($dependencies[$dependencyName])) { + // Dependency added for the first time + $dependencies[$dependencyName] = $dependencyVersion; + + //Factor in the package dependencies too + $dependencies = $this->calculateMergedDependenciesOfPackage($dependencyName, $dependencies); + } elseif ($dependencyVersion !== '*') { + // Dependency already added by another package + // If this package requires a version higher than the currently stored one, store this requirement instead + $currentDependencyVersion = $dependencies[$dependencyName]; + $currently_stored_version_number = $this->calculateVersionNumberFromDependencyVersion($currentDependencyVersion); + + $currently_stored_version_is_in_next_significant_release_format = false; + if ($this->versionFormatIsNextSignificantRelease($currentDependencyVersion)) { + $currently_stored_version_is_in_next_significant_release_format = true; + } + + if (!$currently_stored_version_number) { + $currently_stored_version_number = '*'; + } + + $current_package_version_number = $this->calculateVersionNumberFromDependencyVersion($dependencyVersion); + if (!$current_package_version_number) { + throw new RuntimeException("Bad format for version of dependency {$dependencyName} for package {$packageName}", 1); + } + + $current_package_version_is_in_next_significant_release_format = false; + if ($this->versionFormatIsNextSignificantRelease($dependencyVersion)) { + $current_package_version_is_in_next_significant_release_format = true; + } + + //If I had stored '*', change right away with the more specific version required + if ($currently_stored_version_number === '*') { + $dependencies[$dependencyName] = $dependencyVersion; + } elseif (!$currently_stored_version_is_in_next_significant_release_format && !$current_package_version_is_in_next_significant_release_format) { + //Comparing versions equals or higher, a simple version_compare is enough + if (version_compare($currently_stored_version_number, $current_package_version_number) === -1) { + //Current package version is higher + $dependencies[$dependencyName] = $dependencyVersion; + } + } else { + $compatible = $this->checkNextSignificantReleasesAreCompatible($currently_stored_version_number, $current_package_version_number); + if (!$compatible) { + throw new RuntimeException("Dependency {$dependencyName} is required in two incompatible versions", 2); + } + } + } + } + + return $dependencies; + } + + /** + * Calculates and merges the dependencies of the passed packages + * + * @param array $packages + * @return array + */ + public function calculateMergedDependenciesOfPackages($packages) + { + $dependencies = []; + + foreach ($packages as $package) { + $dependencies = $this->calculateMergedDependenciesOfPackage($package, $dependencies); + } + + return $dependencies; + } + + /** + * Returns the actual version from a dependency version string. + * Examples: + * $versionInformation == '~2.0' => returns '2.0' + * $versionInformation == '>=2.0.2' => returns '2.0.2' + * $versionInformation == '2.0.2' => returns '2.0.2' + * $versionInformation == '*' => returns null + * $versionInformation == '' => returns null + * + * @param string $version + * @return string|null + */ + public function calculateVersionNumberFromDependencyVersion($version) + { + if ($version === '*') { + return null; + } + if ($version === '') { + return null; + } + if ($this->versionFormatIsNextSignificantRelease($version)) { + return trim(substr($version, 1)); + } + if ($this->versionFormatIsEqualOrHigher($version)) { + return trim(substr($version, 2)); + } + + return $version; + } + + /** + * Check if the passed version information contains next significant release (tilde) operator + * + * Example: returns true for $version: '~2.0' + * + * @param string $version + * @return bool + */ + public function versionFormatIsNextSignificantRelease($version): bool + { + return strpos($version, '~') === 0; + } + + /** + * Check if the passed version information contains equal or higher operator + * + * Example: returns true for $version: '>=2.0' + * + * @param string $version + * @return bool + */ + public function versionFormatIsEqualOrHigher($version): bool + { + return strpos($version, '>=') === 0; + } + + /** + * Check if two releases are compatible by next significant release + * + * ~1.2 is equivalent to >=1.2 <2.0.0 + * ~1.2.3 is equivalent to >=1.2.3 <1.3.0 + * + * In short, allows the last digit specified to go up + * + * @param string $version1 the version string (e.g. '2.0.0' or '1.0') + * @param string $version2 the version string (e.g. '2.0.0' or '1.0') + * @return bool + */ + public function checkNextSignificantReleasesAreCompatible($version1, $version2): bool + { + $version1array = explode('.', $version1); + $version2array = explode('.', $version2); + + if (count($version1array) > count($version2array)) { + [$version1array, $version2array] = [$version2array, $version1array]; + } + + $i = 0; + while ($i < count($version1array) - 1) { + if ($version1array[$i] !== $version2array[$i]) { + return false; + } + $i++; + } + + return true; + } +} diff --git a/system/src/Grav/Common/GPM/Installer.php b/system/src/Grav/Common/GPM/Installer.php new file mode 100644 index 0000000..84d6562 --- /dev/null +++ b/system/src/Grav/Common/GPM/Installer.php @@ -0,0 +1,544 @@ + true, + 'ignore_symlinks' => true, + 'sophisticated' => false, + 'theme' => false, + 'install_path' => '', + 'ignores' => [], + 'exclude_checks' => [self::EXISTS, self::NOT_FOUND, self::IS_LINK] + ]; + + /** + * Installs a given package to a given destination. + * + * @param string $zip the local path to ZIP package + * @param string $destination The local path to the Grav Instance + * @param array $options Options to use for installing. ie, ['install_path' => 'user/themes/antimatter'] + * @param string|null $extracted The local path to the extacted ZIP package + * @param bool $keepExtracted True if you want to keep the original files + * @return bool True if everything went fine, False otherwise. + */ + public static function install($zip, $destination, $options = [], $extracted = null, $keepExtracted = false) + { + $destination = rtrim($destination, DS); + $options = array_merge(self::$options, $options); + $install_path = rtrim($destination . DS . ltrim($options['install_path'], DS), DS); + + if (!self::isGravInstance($destination) || !self::isValidDestination( + $install_path, + $options['exclude_checks'] + ) + ) { + return false; + } + + if ((self::lastErrorCode() === self::IS_LINK && $options['ignore_symlinks']) || + (self::lastErrorCode() === self::EXISTS && !$options['overwrite']) + ) { + return false; + } + + // Create a tmp location + $tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true); + $tmp = $tmp_dir . '/Grav-' . uniqid('', false); + + if (!$extracted) { + $extracted = self::unZip($zip, $tmp); + if (!$extracted) { + Folder::delete($tmp); + return false; + } + } + + if (!file_exists($extracted)) { + self::$error = self::INVALID_SOURCE; + return false; + } + + $is_install = true; + $installer = self::loadInstaller($extracted, $is_install); + + if (isset($options['is_update']) && $options['is_update'] === true) { + $method = 'preUpdate'; + } else { + $method = 'preInstall'; + } + + if ($installer && method_exists($installer, $method)) { + $method_result = $installer::$method(); + if ($method_result !== true) { + self::$error = 'An error occurred'; + if (is_string($method_result)) { + self::$error = $method_result; + } + + return false; + } + } + + if (!$options['sophisticated']) { + $isTheme = $options['theme'] ?? false; + // Make sure that themes are always being copied, even if option was not set! + $isTheme = $isTheme || preg_match('|/themes/[^/]+|ui', $install_path); + if ($isTheme) { + self::copyInstall($extracted, $install_path); + } else { + self::moveInstall($extracted, $install_path); + } + } else { + self::sophisticatedInstall($extracted, $install_path, $options['ignores'], $keepExtracted); + } + + Folder::delete($tmp); + + if (isset($options['is_update']) && $options['is_update'] === true) { + $method = 'postUpdate'; + } else { + $method = 'postInstall'; + } + + self::$message = ''; + if ($installer && method_exists($installer, $method)) { + self::$message = $installer::$method(); + } + + self::$error = self::OK; + + return true; + } + + /** + * Unzip a file to somewhere + * + * @param string $zip_file + * @param string $destination + * @return string|false + */ + public static function unZip($zip_file, $destination) + { + $zip = new ZipArchive(); + $archive = $zip->open($zip_file); + + if ($archive === true) { + Folder::create($destination); + + $unzip = $zip->extractTo($destination); + + + if (!$unzip) { + self::$error = self::ZIP_EXTRACT_ERROR; + Folder::delete($destination); + $zip->close(); + return false; + } + + $package_folder_name = $zip->getNameIndex(0); + if ($package_folder_name === false) { + throw new \RuntimeException('Bad package file: ' . Utils::basename($zip_file)); + } + $package_folder_name = preg_replace('#\./$#', '', $package_folder_name); + $zip->close(); + + return $destination . '/' . $package_folder_name; + } + + self::$error = self::ZIP_EXTRACT_ERROR; + self::$error_zip = $archive; + + return false; + } + + /** + * Instantiates and returns the package installer class + * + * @param string $installer_file_folder The folder path that contains install.php + * @param bool $is_install True if install, false if removal + * @return string|null + */ + private static function loadInstaller($installer_file_folder, $is_install) + { + $installer_file_folder = rtrim($installer_file_folder, DS); + + $install_file = $installer_file_folder . DS . 'install.php'; + + if (!file_exists($install_file)) { + return null; + } + + require_once $install_file; + + if ($is_install) { + $slug = ''; + if (($pos = strpos($installer_file_folder, 'grav-plugin-')) !== false) { + $slug = substr($installer_file_folder, $pos + strlen('grav-plugin-')); + } elseif (($pos = strpos($installer_file_folder, 'grav-theme-')) !== false) { + $slug = substr($installer_file_folder, $pos + strlen('grav-theme-')); + } + } else { + $path_elements = explode('/', $installer_file_folder); + $slug = end($path_elements); + } + + if (!$slug) { + return null; + } + + $class_name = ucfirst($slug) . 'Install'; + + if (class_exists($class_name)) { + return $class_name; + } + + $class_name_alphanumeric = preg_replace('/[^a-zA-Z0-9]+/', '', $class_name) ?? $class_name; + + if (class_exists($class_name_alphanumeric)) { + return $class_name_alphanumeric; + } + + return null; + } + + /** + * @param string $source_path + * @param string $install_path + * @return bool + */ + public static function moveInstall($source_path, $install_path) + { + if (file_exists($install_path)) { + Folder::delete($install_path); + } + + Folder::move($source_path, $install_path); + + return true; + } + + /** + * @param string $source_path + * @param string $install_path + * @return bool + */ + public static function copyInstall($source_path, $install_path) + { + if (empty($source_path)) { + throw new RuntimeException("Directory $source_path is missing"); + } + + Folder::rcopy($source_path, $install_path); + + return true; + } + + /** + * @param string $source_path + * @param string $install_path + * @param array $ignores + * @param bool $keep_source + * @return bool + */ + public static function sophisticatedInstall($source_path, $install_path, $ignores = [], $keep_source = false) + { + foreach (new DirectoryIterator($source_path) as $file) { + if ($file->isLink() || $file->isDot() || in_array($file->getFilename(), $ignores, true)) { + continue; + } + + $path = $install_path . DS . $file->getFilename(); + + if ($file->isDir()) { + Folder::delete($path); + if ($keep_source) { + Folder::copy($file->getPathname(), $path); + } else { + Folder::move($file->getPathname(), $path); + } + + if ($file->getFilename() === 'bin') { + $glob = glob($path . DS . '*') ?: []; + foreach ($glob as $bin_file) { + @chmod($bin_file, 0755); + } + } + } else { + @unlink($path); + @copy($file->getPathname(), $path); + } + } + + return true; + } + + /** + * Uninstalls one or more given package + * + * @param string $path The slug of the package(s) + * @param array $options Options to use for uninstalling + * @return bool True if everything went fine, False otherwise. + */ + public static function uninstall($path, $options = []) + { + $options = array_merge(self::$options, $options); + if (!self::isValidDestination($path, $options['exclude_checks']) + ) { + return false; + } + + $installer_file_folder = $path; + $is_install = false; + $installer = self::loadInstaller($installer_file_folder, $is_install); + + if ($installer && method_exists($installer, 'preUninstall')) { + $method_result = $installer::preUninstall(); + if ($method_result !== true) { + self::$error = 'An error occurred'; + if (is_string($method_result)) { + self::$error = $method_result; + } + + return false; + } + } + + $result = Folder::delete($path); + + self::$message = ''; + if ($result && $installer && method_exists($installer, 'postUninstall')) { + self::$message = $installer::postUninstall(); + } + + return $result; + } + + /** + * Runs a set of checks on the destination and sets the Error if any + * + * @param string $destination The directory to run validations at + * @param array $exclude An array of constants to exclude from the validation + * @return bool True if validation passed. False otherwise + */ + public static function isValidDestination($destination, $exclude = []) + { + self::$error = 0; + self::$target = $destination; + + if (is_link($destination)) { + self::$error = self::IS_LINK; + } elseif (file_exists($destination)) { + self::$error = self::EXISTS; + } elseif (!file_exists($destination)) { + self::$error = self::NOT_FOUND; + } elseif (!is_dir($destination)) { + self::$error = self::NOT_DIRECTORY; + } + + if (count($exclude) && in_array(self::$error, $exclude, true)) { + return true; + } + + return !self::$error; + } + + /** + * Validates if the given path is a Grav Instance + * + * @param string $target The local path to the Grav Instance + * @return bool True if is a Grav Instance. False otherwise + */ + public static function isGravInstance($target) + { + self::$error = 0; + self::$target = $target; + + if (!file_exists($target . DS . 'index.php') || + !file_exists($target . DS . 'bin') || + !file_exists($target . DS . 'user') || + !file_exists($target . DS . 'system' . DS . 'config' . DS . 'system.yaml') + ) { + self::$error = self::NOT_GRAV_ROOT; + } + + return !self::$error; + } + + /** + * Returns the last message added by the installer + * + * @return string The message + */ + public static function getMessage() + { + return self::$message; + } + + /** + * Returns the last error occurred in a string message format + * + * @return string The message of the last error + */ + public static function lastErrorMsg() + { + if (is_string(self::$error)) { + return self::$error; + } + + switch (self::$error) { + case 0: + $msg = 'No Error'; + break; + + case self::EXISTS: + $msg = 'The target path "' . self::$target . '" already exists'; + break; + + case self::IS_LINK: + $msg = 'The target path "' . self::$target . '" is a symbolic link'; + break; + + case self::NOT_FOUND: + $msg = 'The target path "' . self::$target . '" does not appear to exist'; + break; + + case self::NOT_DIRECTORY: + $msg = 'The target path "' . self::$target . '" does not appear to be a folder'; + break; + + case self::NOT_GRAV_ROOT: + $msg = 'The target path "' . self::$target . '" does not appear to be a Grav instance'; + break; + + case self::ZIP_OPEN_ERROR: + $msg = 'Unable to open the package file'; + break; + + case self::ZIP_EXTRACT_ERROR: + $msg = 'Unable to extract the package. '; + if (self::$error_zip) { + switch (self::$error_zip) { + case ZipArchive::ER_EXISTS: + $msg .= 'File already exists.'; + break; + + case ZipArchive::ER_INCONS: + $msg .= 'Zip archive inconsistent.'; + break; + + case ZipArchive::ER_MEMORY: + $msg .= 'Memory allocation failure.'; + break; + + case ZipArchive::ER_NOENT: + $msg .= 'No such file.'; + break; + + case ZipArchive::ER_NOZIP: + $msg .= 'Not a zip archive.'; + break; + + case ZipArchive::ER_OPEN: + $msg .= "Can't open file."; + break; + + case ZipArchive::ER_READ: + $msg .= 'Read error.'; + break; + + case ZipArchive::ER_SEEK: + $msg .= 'Seek error.'; + break; + } + } + break; + + case self::INVALID_SOURCE: + $msg = 'Invalid source file'; + break; + + default: + $msg = 'Unknown Error'; + break; + } + + return $msg; + } + + /** + * Returns the last error code of the occurred error + * + * @return int|string The code of the last error + */ + public static function lastErrorCode() + { + return self::$error; + } + + /** + * Allows to manually set an error + * + * @param int|string $error the Error code + * @return void + */ + public static function setError($error) + { + self::$error = $error; + } +} diff --git a/system/src/Grav/Common/GPM/Licenses.php b/system/src/Grav/Common/GPM/Licenses.php new file mode 100644 index 0000000..37ec69d --- /dev/null +++ b/system/src/Grav/Common/GPM/Licenses.php @@ -0,0 +1,116 @@ +content(); + $slug = strtolower($slug); + + if ($license && !self::validate($license)) { + return false; + } + + if (!is_string($license)) { + if (isset($data['licenses'][$slug])) { + unset($data['licenses'][$slug]); + } else { + return false; + } + } else { + $data['licenses'][$slug] = $license; + } + + $licenses->save($data); + $licenses->free(); + + return true; + } + + /** + * Returns the license for a Premium package + * + * @param string|null $slug + * @return string[]|string + */ + public static function get($slug = null) + { + $licenses = self::getLicenseFile(); + $data = (array)$licenses->content(); + $licenses->free(); + + if (null === $slug) { + return $data['licenses'] ?? []; + } + + $slug = strtolower($slug); + + return $data['licenses'][$slug] ?? ''; + } + + + /** + * Validates the License format + * + * @param string|null $license + * @return bool + */ + public static function validate($license = null) + { + if (!is_string($license)) { + return false; + } + + return (bool)preg_match('#' . self::$regex. '#', $license); + } + + /** + * Get the License File object + * + * @return FileInterface + */ + public static function getLicenseFile() + { + if (!isset(self::$file)) { + $path = Grav::instance()['locator']->findResource('user-data://') . '/licenses.yaml'; + if (!file_exists($path)) { + touch($path); + } + self::$file = CompiledYamlFile::instance($path); + } + + return self::$file; + } +} diff --git a/system/src/Grav/Common/GPM/Local/AbstractPackageCollection.php b/system/src/Grav/Common/GPM/Local/AbstractPackageCollection.php new file mode 100644 index 0000000..bcc5697 --- /dev/null +++ b/system/src/Grav/Common/GPM/Local/AbstractPackageCollection.php @@ -0,0 +1,34 @@ + $data) { + $data->set('slug', $name); + $this->items[$name] = new Package($data, $this->type); + } + } +} diff --git a/system/src/Grav/Common/GPM/Local/Package.php b/system/src/Grav/Common/GPM/Local/Package.php new file mode 100644 index 0000000..0caf2ad --- /dev/null +++ b/system/src/Grav/Common/GPM/Local/Package.php @@ -0,0 +1,51 @@ +blueprints()->toArray()); + parent::__construct($data, $package_type); + + $this->settings = $package->toArray(); + + $html_description = Parsedown::instance()->line($this->__get('description')); + $this->data->set('slug', $package->__get('slug')); + $this->data->set('description_html', $html_description); + $this->data->set('description_plain', strip_tags($html_description)); + $this->data->set('symlink', is_link(USER_DIR . $package_type . DS . $this->__get('slug'))); + } + + /** + * @return bool + */ + public function isEnabled() + { + return (bool)$this->settings['enabled']; + } +} diff --git a/system/src/Grav/Common/GPM/Local/Packages.php b/system/src/Grav/Common/GPM/Local/Packages.php new file mode 100644 index 0000000..9f73e1e --- /dev/null +++ b/system/src/Grav/Common/GPM/Local/Packages.php @@ -0,0 +1,29 @@ + new Plugins(), + 'themes' => new Themes() + ]; + + parent::__construct($items); + } +} diff --git a/system/src/Grav/Common/GPM/Local/Plugins.php b/system/src/Grav/Common/GPM/Local/Plugins.php new file mode 100644 index 0000000..dabbe47 --- /dev/null +++ b/system/src/Grav/Common/GPM/Local/Plugins.php @@ -0,0 +1,33 @@ +all()); + } +} diff --git a/system/src/Grav/Common/GPM/Local/Themes.php b/system/src/Grav/Common/GPM/Local/Themes.php new file mode 100644 index 0000000..31ec3a4 --- /dev/null +++ b/system/src/Grav/Common/GPM/Local/Themes.php @@ -0,0 +1,33 @@ +all()); + } +} diff --git a/system/src/Grav/Common/GPM/Remote/AbstractPackageCollection.php b/system/src/Grav/Common/GPM/Remote/AbstractPackageCollection.php new file mode 100644 index 0000000..2fbf82a --- /dev/null +++ b/system/src/Grav/Common/GPM/Remote/AbstractPackageCollection.php @@ -0,0 +1,81 @@ +get('system.gpm.releases', 'stable'); + $cache_dir = Grav::instance()['locator']->findResource('cache://gpm', true, true); + $this->cache = new FilesystemCache($cache_dir); + + $this->repository = $repository . '?v=' . GRAV_VERSION . '&' . $channel . '=1'; + $this->raw = $this->cache->fetch(md5($this->repository)); + + $this->fetch($refresh, $callback); + foreach (json_decode($this->raw, true) as $slug => $data) { + // Temporarily fix for using multi-sites + if (isset($data['install_path'])) { + $path = preg_replace('~^user/~i', 'user://', $data['install_path']); + $data['install_path'] = Grav::instance()['locator']->findResource($path, false, true); + } + $this->items[$slug] = new Package($data, $this->type); + } + } + + /** + * @param bool $refresh + * @param callable|null $callback + * @return string + */ + public function fetch($refresh = false, $callback = null) + { + if (!$this->raw || $refresh) { + $response = Response::get($this->repository, [], $callback); + $this->raw = $response; + $this->cache->save(md5($this->repository), $this->raw, $this->lifetime); + } + + return $this->raw; + } +} diff --git a/system/src/Grav/Common/GPM/Remote/GravCore.php b/system/src/Grav/Common/GPM/Remote/GravCore.php new file mode 100644 index 0000000..4700e7d --- /dev/null +++ b/system/src/Grav/Common/GPM/Remote/GravCore.php @@ -0,0 +1,151 @@ +get('system.gpm.releases', 'stable'); + $cache_dir = Grav::instance()['locator']->findResource('cache://gpm', true, true); + $this->cache = new FilesystemCache($cache_dir); + $this->repository .= '?v=' . GRAV_VERSION . '&' . $channel . '=1'; + $this->raw = $this->cache->fetch(md5($this->repository)); + + $this->fetch($refresh, $callback); + + $this->data = json_decode($this->raw, true); + $this->version = $this->data['version'] ?? '-'; + $this->date = $this->data['date'] ?? '-'; + $this->min_php = $this->data['min_php'] ?? null; + + if (isset($this->data['assets'])) { + foreach ((array)$this->data['assets'] as $slug => $data) { + $this->items[$slug] = new Package($data); + } + } + } + + /** + * Returns the list of assets associated to the latest version of Grav + * + * @return array list of assets + */ + public function getAssets() + { + return $this->data['assets']; + } + + /** + * Returns the changelog list for each version of Grav + * + * @param string|null $diff the version number to start the diff from + * @return array changelog list for each version + */ + public function getChangelog($diff = null) + { + if (!$diff) { + return $this->data['changelog']; + } + + $diffLog = []; + foreach ((array)$this->data['changelog'] as $version => $changelog) { + preg_match("/[\w\-\.]+/", $version, $cleanVersion); + + if (!$cleanVersion || version_compare($diff, $cleanVersion[0], '>=')) { + continue; + } + + $diffLog[$version] = $changelog; + } + + return $diffLog; + } + + /** + * Return the release date of the latest Grav + * + * @return string + */ + public function getDate() + { + return $this->date; + } + + /** + * Determine if this version of Grav is eligible to be updated + * + * @return mixed + */ + public function isUpdatable() + { + return version_compare(GRAV_VERSION, $this->getVersion(), '<'); + } + + /** + * Returns the latest version of Grav available remotely + * + * @return string + */ + public function getVersion() + { + return $this->version; + } + + /** + * Returns the minimum PHP version + * + * @return string + */ + public function getMinPHPVersion() + { + // If non min set, assume current PHP version + if (null === $this->min_php) { + $this->min_php = PHP_VERSION; + } + + return $this->min_php; + } + + /** + * Is this installation symlinked? + * + * @return bool + */ + public function isSymlink() + { + return is_link(GRAV_ROOT . DS . 'index.php'); + } +} diff --git a/system/src/Grav/Common/GPM/Remote/Package.php b/system/src/Grav/Common/GPM/Remote/Package.php new file mode 100644 index 0000000..41db50e --- /dev/null +++ b/system/src/Grav/Common/GPM/Remote/Package.php @@ -0,0 +1,66 @@ +data->toArray(); + } + + /** + * Returns the changelog list for each version of a package + * + * @param string|null $diff the version number to start the diff from + * @return array changelog list for each version + */ + public function getChangelog($diff = null) + { + if (!$diff) { + return $this->data['changelog']; + } + + $diffLog = []; + foreach ((array)$this->data['changelog'] as $version => $changelog) { + preg_match("/[\w\-.]+/", $version, $cleanVersion); + + if (!$cleanVersion || version_compare($diff, $cleanVersion[0], '>=')) { + continue; + } + + $diffLog[$version] = $changelog; + } + + return $diffLog; + } +} diff --git a/system/src/Grav/Common/GPM/Remote/Packages.php b/system/src/Grav/Common/GPM/Remote/Packages.php new file mode 100644 index 0000000..714bad2 --- /dev/null +++ b/system/src/Grav/Common/GPM/Remote/Packages.php @@ -0,0 +1,34 @@ + new Plugins($refresh, $callback), + 'themes' => new Themes($refresh, $callback) + ]; + + parent::__construct($items); + } +} diff --git a/system/src/Grav/Common/GPM/Remote/Plugins.php b/system/src/Grav/Common/GPM/Remote/Plugins.php new file mode 100644 index 0000000..3495566 --- /dev/null +++ b/system/src/Grav/Common/GPM/Remote/Plugins.php @@ -0,0 +1,32 @@ +repository, $refresh, $callback); + } +} diff --git a/system/src/Grav/Common/GPM/Remote/Themes.php b/system/src/Grav/Common/GPM/Remote/Themes.php new file mode 100644 index 0000000..09e3f8a --- /dev/null +++ b/system/src/Grav/Common/GPM/Remote/Themes.php @@ -0,0 +1,32 @@ +repository, $refresh, $callback); + } +} diff --git a/system/src/Grav/Common/GPM/Response.php b/system/src/Grav/Common/GPM/Response.php new file mode 100644 index 0000000..98654b6 --- /dev/null +++ b/system/src/Grav/Common/GPM/Response.php @@ -0,0 +1,3 @@ +remote = new Remote\GravCore($refresh, $callback); + } + + /** + * Returns the release date of the latest version of Grav + * + * @return string + */ + public function getReleaseDate() + { + return $this->remote->getDate(); + } + + /** + * Returns the version of the installed Grav + * + * @return string + */ + public function getLocalVersion() + { + return GRAV_VERSION; + } + + /** + * Returns the version of the remotely available Grav + * + * @return string + */ + public function getRemoteVersion() + { + return $this->remote->getVersion(); + } + + /** + * Returns an array of assets available to download remotely + * + * @return array + */ + public function getAssets() + { + return $this->remote->getAssets(); + } + + /** + * Returns the changelog list for each version of Grav + * + * @param string|null $diff the version number to start the diff from + * @return array return the changelog list for each version + */ + public function getChangelog($diff = null) + { + return $this->remote->getChangelog($diff); + } + + /** + * Make sure this meets minimum PHP requirements + * + * @return bool + */ + public function meetsRequirements() + { + if (version_compare(PHP_VERSION, $this->minPHPVersion(), '<')) { + return false; + } + + return true; + } + + /** + * Get minimum PHP version from remote + * + * @return string + */ + public function minPHPVersion() + { + if (null === $this->min_php) { + $this->min_php = $this->remote->getMinPHPVersion(); + } + + return $this->min_php; + } + + /** + * Checks if the currently installed Grav is upgradable to a newer version + * + * @return bool True if it's upgradable, False otherwise. + */ + public function isUpgradable() + { + return version_compare($this->getLocalVersion(), $this->getRemoteVersion(), '<'); + } + + /** + * Checks if Grav is currently symbolically linked + * + * @return bool True if Grav is symlinked, False otherwise. + */ + public function isSymlink() + { + return $this->remote->isSymlink(); + } +} diff --git a/system/src/Grav/Common/Getters.php b/system/src/Grav/Common/Getters.php new file mode 100644 index 0000000..d16dba5 --- /dev/null +++ b/system/src/Grav/Common/Getters.php @@ -0,0 +1,170 @@ +offsetSet($offset, $value); + } + + /** + * Magic getter method + * + * @param int|string $offset Medium name value + * @return mixed Medium value + */ + #[\ReturnTypeWillChange] + public function __get($offset) + { + return $this->offsetGet($offset); + } + + /** + * Magic method to determine if the attribute is set + * + * @param int|string $offset Medium name value + * @return boolean True if the value is set + */ + #[\ReturnTypeWillChange] + public function __isset($offset) + { + return $this->offsetExists($offset); + } + + /** + * Magic method to unset the attribute + * + * @param int|string $offset The name value to unset + */ + #[\ReturnTypeWillChange] + public function __unset($offset) + { + $this->offsetUnset($offset); + } + + /** + * @param int|string $offset + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($offset) + { + if ($this->gettersVariable) { + $var = $this->gettersVariable; + + return isset($this->{$var}[$offset]); + } + + return isset($this->{$offset}); + } + + /** + * @param int|string $offset + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + if ($this->gettersVariable) { + $var = $this->gettersVariable; + + return $this->{$var}[$offset] ?? null; + } + + return $this->{$offset} ?? null; + } + + /** + * @param int|string $offset + * @param mixed $value + */ + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value) + { + if ($this->gettersVariable) { + $var = $this->gettersVariable; + $this->{$var}[$offset] = $value; + } else { + $this->{$offset} = $value; + } + } + + /** + * @param int|string $offset + */ + #[\ReturnTypeWillChange] + public function offsetUnset($offset) + { + if ($this->gettersVariable) { + $var = $this->gettersVariable; + unset($this->{$var}[$offset]); + } else { + unset($this->{$offset}); + } + } + + /** + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + if ($this->gettersVariable) { + $var = $this->gettersVariable; + return count($this->{$var}); + } + + return count($this->toArray()); + } + + /** + * Returns an associative array of object properties. + * + * @return array + */ + public function toArray() + { + if ($this->gettersVariable) { + $var = $this->gettersVariable; + + return $this->{$var}; + } + + $properties = (array)$this; + $list = []; + foreach ($properties as $property => $value) { + if ($property[0] !== "\0") { + $list[$property] = $value; + } + } + + return $list; + } +} diff --git a/system/src/Grav/Common/Grav.php b/system/src/Grav/Common/Grav.php new file mode 100644 index 0000000..892f1d0 --- /dev/null +++ b/system/src/Grav/Common/Grav.php @@ -0,0 +1,829 @@ + Browser::class, + 'cache' => Cache::class, + 'events' => EventDispatcher::class, + 'exif' => Exif::class, + 'plugins' => Plugins::class, + 'scheduler' => Scheduler::class, + 'taxonomy' => Taxonomy::class, + 'themes' => Themes::class, + 'twig' => Twig::class, + 'uri' => Uri::class, + ]; + + /** + * @var array All middleware processors that are processed in $this->process() + */ + protected $middleware = [ + 'multipartRequestSupport', + 'initializeProcessor', + 'pluginsProcessor', + 'themesProcessor', + 'requestProcessor', + 'tasksProcessor', + 'backupsProcessor', + 'schedulerProcessor', + 'assetsProcessor', + 'twigProcessor', + 'pagesProcessor', + 'debuggerAssetsProcessor', + 'renderProcessor', + ]; + + /** @var array */ + protected $initialized = []; + + /** + * Reset the Grav instance. + * + * @return void + */ + public static function resetInstance(): void + { + if (self::$instance) { + // @phpstan-ignore-next-line + self::$instance = null; + } + } + + /** + * Return the Grav instance. Create it if it's not already instanced + * + * @param array $values + * @return Grav + */ + public static function instance(array $values = []) + { + if (null === self::$instance) { + self::$instance = static::load($values); + + /** @var ClassLoader|null $loader */ + $loader = self::$instance['loader'] ?? null; + if ($loader) { + // Load fix for Deferred Twig Extension + $loader->addPsr4('Phive\\Twig\\Extensions\\Deferred\\', LIB_DIR . 'Phive/Twig/Extensions/Deferred/', true); + } + } elseif ($values) { + $instance = self::$instance; + foreach ($values as $key => $value) { + $instance->offsetSet($key, $value); + } + } + + return self::$instance; + } + + /** + * Get Grav version. + * + * @return string + */ + public function getVersion(): string + { + return GRAV_VERSION; + } + + /** + * @return bool + */ + public function isSetup(): bool + { + return isset($this->initialized['setup']); + } + + /** + * Setup Grav instance using specific environment. + * + * @param string|null $environment + * @return $this + */ + public function setup(string $environment = null) + { + if (isset($this->initialized['setup'])) { + return $this; + } + + $this->initialized['setup'] = true; + + // Force environment if passed to the method. + if ($environment) { + Setup::$environment = $environment; + } + + // Initialize setup and streams. + $this['setup']; + $this['streams']; + + return $this; + } + + /** + * Initialize CLI environment. + * + * Call after `$grav->setup($environment)` + * + * - Load configuration + * - Initialize logger + * - Disable debugger + * - Set timezone, locale + * - Load plugins (call PluginsLoadedEvent) + * - Set Pages and Users type to be used in the site + * + * This method WILL NOT initialize assets, twig or pages. + * + * @return $this + */ + public function initializeCli() + { + InitializeProcessor::initializeCli($this); + + return $this; + } + + /** + * Process a request + * + * @return void + */ + public function process(): void + { + if (isset($this->initialized['process'])) { + return; + } + + // Initialize Grav if needed. + $this->setup(); + + $this->initialized['process'] = true; + + $container = new Container( + [ + 'multipartRequestSupport' => function () { + return new MultipartRequestSupport(); + }, + 'initializeProcessor' => function () { + return new InitializeProcessor($this); + }, + 'backupsProcessor' => function () { + return new BackupsProcessor($this); + }, + 'pluginsProcessor' => function () { + return new PluginsProcessor($this); + }, + 'themesProcessor' => function () { + return new ThemesProcessor($this); + }, + 'schedulerProcessor' => function () { + return new SchedulerProcessor($this); + }, + 'requestProcessor' => function () { + return new RequestProcessor($this); + }, + 'tasksProcessor' => function () { + return new TasksProcessor($this); + }, + 'assetsProcessor' => function () { + return new AssetsProcessor($this); + }, + 'twigProcessor' => function () { + return new TwigProcessor($this); + }, + 'pagesProcessor' => function () { + return new PagesProcessor($this); + }, + 'debuggerAssetsProcessor' => function () { + return new DebuggerAssetsProcessor($this); + }, + 'renderProcessor' => function () { + return new RenderProcessor($this); + }, + ] + ); + + $default = static function () { + return new Response(404, ['Expires' => 0, 'Cache-Control' => 'no-store, max-age=0'], 'Not Found'); + }; + + $collection = new RequestHandler($this->middleware, $default, $container); + + $response = $collection->handle($this['request']); + $body = $response->getBody(); + + /** @var Messages $messages */ + $messages = $this['messages']; + + // Prevent caching if session messages were displayed in the page. + $noCache = $messages->isCleared(); + if ($noCache) { + $response = $response->withHeader('Cache-Control', 'no-store, max-age=0'); + } + + // Handle ETag and If-None-Match headers. + if ($response->getHeaderLine('ETag') === '1') { + $etag = md5($body); + $response = $response->withHeader('ETag', '"' . $etag . '"'); + + $search = trim($this['request']->getHeaderLine('If-None-Match'), '"'); + if ($noCache === false && $search === $etag) { + $response = $response->withStatus(304); + $body = ''; + } + } + + // Echo page content. + $this->header($response); + echo $body; + + $this['debugger']->render(); + + // Response object can turn off all shutdown processing. This can be used for example to speed up AJAX responses. + // Note that using this feature will also turn off response compression. + if ($response->getHeaderLine('Grav-Internal-SkipShutdown') !== '1') { + register_shutdown_function([$this, 'shutdown']); + } + } + + /** + * Clean any output buffers. Useful when exiting from the application. + * + * Please use $grav->close() and $grav->redirect() instead of calling this one! + * + * @return void + */ + public function cleanOutputBuffers(): void + { + // Make sure nothing extra gets written to the response. + while (ob_get_level()) { + ob_end_clean(); + } + // Work around PHP bug #8218 (8.0.17 & 8.1.4). + header_remove('Content-Encoding'); + } + + /** + * Terminates Grav request with a response. + * + * Please use this method instead of calling `die();` or `exit();`. Note that you need to create a response object. + * + * @param ResponseInterface $response + * @return never-return + */ + public function close(ResponseInterface $response): void + { + $this->cleanOutputBuffers(); + + // Close the session. + if (isset($this['session'])) { + $this['session']->close(); + } + + /** @var ServerRequestInterface $request */ + $request = $this['request']; + + /** @var Debugger $debugger */ + $debugger = $this['debugger']; + $response = $debugger->logRequest($request, $response); + + $body = $response->getBody(); + + /** @var Messages $messages */ + $messages = $this['messages']; + + // Prevent caching if session messages were displayed in the page. + $noCache = $messages->isCleared(); + if ($noCache) { + $response = $response->withHeader('Cache-Control', 'no-store, max-age=0'); + } + + // Handle ETag and If-None-Match headers. + if ($response->getHeaderLine('ETag') === '1') { + $etag = md5($body); + $response = $response->withHeader('ETag', '"' . $etag . '"'); + + $search = trim($this['request']->getHeaderLine('If-None-Match'), '"'); + if ($noCache === false && $search === $etag) { + $response = $response->withStatus(304); + $body = ''; + } + } + + // Echo page content. + $this->header($response); + echo $body; + exit(); + } + + /** + * @param ResponseInterface $response + * @return never-return + * @deprecated 1.7 Use $grav->close() instead. + */ + public function exit(ResponseInterface $response): void + { + $this->close($response); + } + + /** + * Terminates Grav request and redirects browser to another location. + * + * Please use this method instead of calling `header("Location: {$url}", true, 302); exit();`. + * + * @param Route|string $route Internal route. + * @param int|null $code Redirection code (30x) + * @return never-return + */ + public function redirect($route, $code = null): void + { + $response = $this->getRedirectResponse($route, $code); + + $this->close($response); + } + + /** + * Returns redirect response object from Grav. + * + * @param Route|string $route Internal route. + * @param int|null $code Redirection code (30x) + * @return ResponseInterface + */ + public function getRedirectResponse($route, $code = null): ResponseInterface + { + /** @var Uri $uri */ + $uri = $this['uri']; + + if (is_string($route)) { + // Clean route for redirect + $route = preg_replace("#^\/[\\\/]+\/#", '/', $route); + + if (null === $code) { + // Check for redirect code in the route: e.g. /new/[301], /new[301]/route or /new[301].html + $regex = '/.*(\[(30[1-7])\])(.\w+|\/.*?)?$/'; + preg_match($regex, $route, $matches); + if ($matches) { + $route = str_replace($matches[1], '', $matches[0]); + $code = $matches[2]; + } + } + + if ($uri::isExternal($route)) { + $url = $route; + } else { + $url = rtrim($uri->rootUrl(), '/') . '/'; + + if ($this['config']->get('system.pages.redirect_trailing_slash', true)) { + $url .= trim($route, '/'); // Remove trailing slash + } else { + $url .= ltrim($route, '/'); // Support trailing slash default routes + } + } + } elseif ($route instanceof Route) { + $url = $route->toString(true); + } else { + throw new InvalidArgumentException('Bad $route'); + } + + if ($code < 300 || $code > 399) { + $code = null; + } + + if ($code === null) { + $code = $this['config']->get('system.pages.redirect_default_code', 302); + } + + if ($uri->extension() === 'json') { + return new Response(200, ['Content-Type' => 'application/json'], json_encode(['code' => $code, 'redirect' => $url], JSON_THROW_ON_ERROR)); + } + + return new Response($code, ['Location' => $url]); + } + + /** + * Redirect browser to another location taking language into account (preferred) + * + * @param string $route Internal route. + * @param int $code Redirection code (30x) + * @return void + */ + public function redirectLangSafe($route, $code = null): void + { + if (!$this['uri']->isExternal($route)) { + $this->redirect($this['pages']->route($route), $code); + } else { + $this->redirect($route, $code); + } + } + + /** + * Set response header. + * + * @param ResponseInterface|null $response + * @return void + */ + public function header(ResponseInterface $response = null): void + { + if (null === $response) { + /** @var PageInterface $page */ + $page = $this['page']; + $response = new Response($page->httpResponseCode(), $page->httpHeaders(), ''); + } + + header("HTTP/{$response->getProtocolVersion()} {$response->getStatusCode()} {$response->getReasonPhrase()}"); + foreach ($response->getHeaders() as $key => $values) { + // Skip internal Grav headers. + if (strpos($key, 'Grav-Internal-') === 0) { + continue; + } + foreach ($values as $i => $value) { + header($key . ': ' . $value, $i === 0); + } + } + } + + /** + * Set the system locale based on the language and configuration + * + * @return void + */ + public function setLocale(): void + { + // Initialize Locale if set and configured. + if ($this['language']->enabled() && $this['config']->get('system.languages.override_locale')) { + $language = $this['language']->getLanguage(); + setlocale(LC_ALL, strlen($language) < 3 ? ($language . '_' . strtoupper($language)) : $language); + } elseif ($this['config']->get('system.default_locale')) { + setlocale(LC_ALL, $this['config']->get('system.default_locale')); + } + } + + /** + * @param object $event + * @return object + */ + public function dispatchEvent($event) + { + /** @var EventDispatcherInterface $events */ + $events = $this['events']; + $eventName = get_class($event); + + $timestamp = microtime(true); + $event = $events->dispatch($event); + + /** @var Debugger $debugger */ + $debugger = $this['debugger']; + $debugger->addEvent($eventName, $event, $events, $timestamp); + + return $event; + } + + /** + * Fires an event with optional parameters. + * + * @param string $eventName + * @param Event|null $event + * @return Event + */ + public function fireEvent($eventName, Event $event = null) + { + /** @var EventDispatcherInterface $events */ + $events = $this['events']; + if (null === $event) { + $event = new Event(); + } + + $timestamp = microtime(true); + $events->dispatch($event, $eventName); + + /** @var Debugger $debugger */ + $debugger = $this['debugger']; + $debugger->addEvent($eventName, $event, $events, $timestamp); + + return $event; + } + + /** + * Set the final content length for the page and flush the buffer + * + * @return void + */ + public function shutdown(): void + { + // Prevent user abort allowing onShutdown event to run without interruptions. + if (function_exists('ignore_user_abort')) { + @ignore_user_abort(true); + } + + // Close the session allowing new requests to be handled. + if (isset($this['session'])) { + $this['session']->close(); + } + + /** @var Config $config */ + $config = $this['config']; + if ($config->get('system.debugger.shutdown.close_connection', true)) { + // Flush the response and close the connection to allow time consuming tasks to be performed without leaving + // the connection to the client open. This will make page loads to feel much faster. + + // FastCGI allows us to flush all response data to the client and finish the request. + $success = function_exists('fastcgi_finish_request') ? @fastcgi_finish_request() : false; + if (!$success) { + // Unfortunately without FastCGI there is no way to force close the connection. + // We need to ask browser to close the connection for us. + + if ($config->get('system.cache.gzip')) { + // Flush gzhandler buffer if gzip setting was enabled to get the size of the compressed output. + ob_end_flush(); + } elseif ($config->get('system.cache.allow_webserver_gzip')) { + // Let web server to do the hard work. + header('Content-Encoding: identity'); + } elseif (function_exists('apache_setenv')) { + // Without gzip we have no other choice than to prevent server from compressing the output. + // This action turns off mod_deflate which would prevent us from closing the connection. + @apache_setenv('no-gzip', '1'); + } else { + // Fall back to unknown content encoding, it prevents most servers from deflating the content. + header('Content-Encoding: none'); + } + + // Get length and close the connection. + header('Content-Length: ' . ob_get_length()); + header('Connection: close'); + + ob_end_flush(); + @ob_flush(); + flush(); + } + } + + // Run any time consuming tasks. + $this->fireEvent('onShutdown'); + } + + /** + * Magic Catch All Function + * + * Used to call closures. + * + * Source: http://stackoverflow.com/questions/419804/closures-as-class-members + * + * @param string $method + * @param array $args + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function __call($method, $args) + { + $closure = $this->{$method} ?? null; + + return is_callable($closure) ? $closure(...$args) : null; + } + + /** + * Measure how long it takes to do an action. + * + * @param string $timerId + * @param string $timerTitle + * @param callable $callback + * @return mixed Returns value returned by the callable. + */ + public function measureTime(string $timerId, string $timerTitle, callable $callback) + { + $debugger = $this['debugger']; + $debugger->startTimer($timerId, $timerTitle); + $result = $callback(); + $debugger->stopTimer($timerId); + + return $result; + } + + /** + * Initialize and return a Grav instance + * + * @param array $values + * @return static + */ + protected static function load(array $values) + { + $container = new static($values); + + $container['debugger'] = new Debugger(); + $container['grav'] = function (Container $container) { + user_error('Calling $grav[\'grav\'] or {{ grav.grav }} is deprecated since Grav 1.6, just use $grav or {{ grav }}', E_USER_DEPRECATED); + + return $container; + }; + + $container->registerServices(); + + return $container; + } + + /** + * Register all services + * Services are defined in the diMap. They can either only the class + * of a Service Provider or a pair of serviceKey => serviceClass that + * gets directly mapped into the container. + * + * @return void + */ + protected function registerServices(): void + { + foreach (self::$diMap as $serviceKey => $serviceClass) { + if (is_int($serviceKey)) { + $this->register(new $serviceClass); + } else { + $this[$serviceKey] = function ($c) use ($serviceClass) { + return new $serviceClass($c); + }; + } + } + } + + /** + * This attempts to find media, other files, and download them + * + * @param string $path + * @return PageInterface|false + */ + public function fallbackUrl($path) + { + $path_parts = Utils::pathinfo($path); + if (!is_array($path_parts)) { + return false; + } + + /** @var Uri $uri */ + $uri = $this['uri']; + + /** @var Config $config */ + $config = $this['config']; + + /** @var Pages $pages */ + $pages = $this['pages']; + $page = $pages->find($path_parts['dirname'], true); + + $uri_extension = strtolower($uri->extension() ?? ''); + $fallback_types = $config->get('system.media.allowed_fallback_types'); + $supported_types = $config->get('media.types'); + + $parsed_url = parse_url(rawurldecode($uri->basename())); + $media_file = $parsed_url['path']; + + $event = new Event([ + 'uri' => $uri, + 'page' => &$page, + 'filename' => &$media_file, + 'extension' => $uri_extension, + 'allowed_fallback_types' => &$fallback_types, + 'media_types' => &$supported_types + ]); + + $this->fireEvent('onPageFallBackUrl', $event); + + // Check whitelist first, then ensure extension is a valid media type + if (!empty($fallback_types) && !in_array($uri_extension, $fallback_types, true)) { + return false; + } + if (!array_key_exists($uri_extension, $supported_types)) { + return false; + } + + if ($page) { + $media = $page->media()->all(); + + // if this is a media object, try actions first + if (isset($media[$media_file])) { + /** @var Medium $medium */ + $medium = $media[$media_file]; + foreach ($uri->query(null, true) as $action => $params) { + if (in_array($action, ImageMedium::$magic_actions, true)) { + call_user_func_array([&$medium, $action], explode(',', $params)); + } + } + Utils::download($medium->path(), false); + } + + // unsupported media type, try to download it... + if ($uri_extension) { + $extension = $uri_extension; + } elseif (isset($path_parts['extension'])) { + $extension = $path_parts['extension']; + } else { + $extension = null; + } + + if ($extension) { + $download = true; + if (in_array(ltrim($extension, '.'), $config->get('system.media.unsupported_inline_types', []), true)) { + $download = false; + } + Utils::download($page->path() . DIRECTORY_SEPARATOR . $uri->basename(), $download); + } + } + + // Nothing found + return false; + } +} diff --git a/system/src/Grav/Common/GravTrait.php b/system/src/Grav/Common/GravTrait.php new file mode 100644 index 0000000..0472e61 --- /dev/null +++ b/system/src/Grav/Common/GravTrait.php @@ -0,0 +1,34 @@ + 'Grav CMS' + ]; + + public static function getClient(array $overrides = [], int $connections = 6, callable $callback = null): HttpClientInterface + { + $config = Grav::instance()['config']; + $options = static::getOptions(); + + // Use callback if provided + if ($callback) { + self::$callback = $callback; + $options->setOnProgress([Client::class, 'progress']); + } + + $settings = array_merge($options->toArray(), $overrides); + $preferred_method = $config->get('system.http.method'); + // Try old GPM setting if value is the same as system default + if ($preferred_method === 'auto') { + $preferred_method = $config->get('system.gpm.method', 'auto'); + } + + switch ($preferred_method) { + case 'curl': + $client = new CurlHttpClient($settings, $connections); + break; + case 'fopen': + case 'native': + $client = new NativeHttpClient($settings, $connections); + break; + default: + $client = HttpClient::create($settings, $connections); + } + + return $client; + } + + /** + * Get HTTP Options + * + * @return HttpOptions + */ + public static function getOptions(): HttpOptions + { + $config = Grav::instance()['config']; + $referer = defined('GRAV_CLI') ? 'grav_cli' : Grav::instance()['uri']->rootUrl(true); + + $options = new HttpOptions(); + + // Set default Headers + $options->setHeaders(array_merge([ 'Referer' => $referer ], self::$headers)); + + // Disable verify Peer if required + $verify_peer = $config->get('system.http.verify_peer'); + // Try old GPM setting if value is default + if ($verify_peer === true) { + $verify_peer = $config->get('system.gpm.verify_peer', null) ?? $verify_peer; + } + $options->verifyPeer($verify_peer); + + // Set verify Host + $verify_host = $config->get('system.http.verify_host', true); + $options->verifyHost($verify_host); + + // New setting and must be enabled for Proxy to work + if ($config->get('system.http.enable_proxy', true)) { + // Set proxy url if provided + $proxy_url = $config->get('system.http.proxy_url', $config->get('system.gpm.proxy_url', null)); + if ($proxy_url !== null) { + $options->setProxy($proxy_url); + } + + // Certificate + $proxy_cert = $config->get('system.http.proxy_cert_path', null); + if ($proxy_cert !== null) { + $options->setCaPath($proxy_cert); + } + } + + return $options; + } + + /** + * Progress normalized for cURL and Fopen + * Accepts a variable length of arguments passed in by stream method + * + * @return void + */ + public static function progress(int $bytes_transferred, int $filesize, array $info) + { + + if ($bytes_transferred > 0) { + $percent = $filesize <= 0 ? 0 : (int)(($bytes_transferred * 100) / $filesize); + + $progress = [ + 'code' => $info['http_code'], + 'filesize' => $filesize, + 'transferred' => $bytes_transferred, + 'percent' => $percent < 100 ? $percent : 100 + ]; + + if (self::$callback !== null) { + call_user_func(self::$callback, $progress); + } + } + } +} diff --git a/system/src/Grav/Common/HTTP/Response.php b/system/src/Grav/Common/HTTP/Response.php new file mode 100644 index 0000000..42c676e --- /dev/null +++ b/system/src/Grav/Common/HTTP/Response.php @@ -0,0 +1,96 @@ +getContent(); + } + + + /** + * Makes a request to the URL by using the preferred method + * + * @param string $method method to call such as GET, PUT, etc + * @param string $uri URL to call + * @param array $overrides An array of parameters for both `curl` and `fopen` + * @param callable|null $callback Either a function or callback in array notation + * @return ResponseInterface + * @throws TransportExceptionInterface + */ + public static function request(string $method, string $uri, array $overrides = [], callable $callback = null): ResponseInterface + { + if (empty($method)) { + throw new TransportException('missing method (GET, PUT, etc.)'); + } + + if (empty($uri)) { + throw new TransportException('missing URI'); + } + + // check if this function is available, if so use it to stop any timeouts + try { + if (Utils::functionExists('set_time_limit')) { + @set_time_limit(0); + } + } catch (Exception $e) {} + + $client = Client::getClient($overrides, 6, $callback); + + return $client->request($method, $uri); + } + + + /** + * Is this a remote file or not + * + * @param string $file + * @return bool + */ + public static function isRemote($file): bool + { + return (bool) filter_var($file, FILTER_VALIDATE_URL); + } + + +} diff --git a/system/src/Grav/Common/Helpers/Base32.php b/system/src/Grav/Common/Helpers/Base32.php new file mode 100644 index 0000000..32b0fec --- /dev/null +++ b/system/src/Grav/Common/Helpers/Base32.php @@ -0,0 +1,141 @@ +', '?' + 0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06, // '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G' + 0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, // 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O' + 0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, // 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W' + 0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF, // 'X', 'Y', 'Z', '[', '\', ']', '^', '_' + 0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06, // '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g' + 0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, // 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o' + 0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, // 'p', 'q', 'r', 's', 't', 'u', 'v', 'w' + 0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF // 'x', 'y', 'z', '{', '|', '}', '~', 'DEL' + ]; + + /** + * Encode in Base32 + * + * @param string $bytes + * @return string + */ + public static function encode($bytes) + { + $i = 0; + $index = 0; + $base32 = ''; + $bytesLen = strlen($bytes); + + while ($i < $bytesLen) { + $currByte = ord($bytes[$i]); + + /* Is the current digit going to span a byte boundary? */ + if ($index > 3) { + if (($i + 1) < $bytesLen) { + $nextByte = ord($bytes[$i+1]); + } else { + $nextByte = 0; + } + + $digit = $currByte & (0xFF >> $index); + $index = ($index + 5) % 8; + $digit <<= $index; + $digit |= $nextByte >> (8 - $index); + $i++; + } else { + $digit = ($currByte >> (8 - ($index + 5))) & 0x1F; + $index = ($index + 5) % 8; + if ($index === 0) { + $i++; + } + } + + $base32 .= self::$base32Chars[$digit]; + } + return $base32; + } + + /** + * Decode in Base32 + * + * @param string $base32 + * @return string + */ + public static function decode($base32) + { + $bytes = []; + $base32Len = strlen($base32); + $base32LookupLen = count(self::$base32Lookup); + + for ($i = $base32Len * 5 / 8 - 1; $i >= 0; --$i) { + $bytes[] = 0; + } + + for ($i = 0, $index = 0, $offset = 0; $i < $base32Len; $i++) { + $lookup = ord($base32[$i]) - ord('0'); + + /* Skip chars outside the lookup table */ + if ($lookup < 0 || $lookup >= $base32LookupLen) { + continue; + } + + $digit = self::$base32Lookup[$lookup]; + + /* If this digit is not in the table, ignore it */ + if ($digit === 0xFF) { + continue; + } + + if ($index <= 3) { + $index = ($index + 5) % 8; + if ($index === 0) { + $bytes[$offset] |= $digit; + $offset++; + if ($offset >= count($bytes)) { + break; + } + } else { + $bytes[$offset] |= $digit << (8 - $index); + } + } else { + $index = ($index + 5) % 8; + $bytes[$offset] |= ($digit >> $index); + $offset++; + if ($offset >= count($bytes)) { + break; + } + $bytes[$offset] |= $digit << (8 - $index); + } + } + + $bites = ''; + foreach ($bytes as $byte) { + $bites .= chr($byte); + } + + return $bites; + } +} diff --git a/system/src/Grav/Common/Helpers/Excerpts.php b/system/src/Grav/Common/Helpers/Excerpts.php new file mode 100644 index 0000000..cdfb33b --- /dev/null +++ b/system/src/Grav/Common/Helpers/Excerpts.php @@ -0,0 +1,196 @@ +` + * @param PageInterface|null $page Page, defaults to the current page object + * @return string Returns final HTML string + */ + public static function processImageHtml($html, PageInterface $page = null) + { + $excerpt = static::getExcerptFromHtml($html, 'img'); + if (null === $excerpt) { + return ''; + } + + $original_src = $excerpt['element']['attributes']['src']; + $excerpt['element']['attributes']['href'] = $original_src; + + $excerpt = static::processLinkExcerpt($excerpt, $page, 'image'); + + $excerpt['element']['attributes']['src'] = $excerpt['element']['attributes']['href']; + unset($excerpt['element']['attributes']['href']); + + $excerpt = static::processImageExcerpt($excerpt, $page); + + $excerpt['element']['attributes']['data-src'] = $original_src; + + $html = static::getHtmlFromExcerpt($excerpt); + + return $html; + } + + /** + * Process Grav page link URL from HTML tag + * + * @param string $html HTML tag e.g. `Page Link` + * @param PageInterface|null $page Page, defaults to the current page object + * @return string Returns final HTML string + */ + public static function processLinkHtml($html, PageInterface $page = null) + { + $excerpt = static::getExcerptFromHtml($html, 'a'); + if (null === $excerpt) { + return ''; + } + + $original_href = $excerpt['element']['attributes']['href']; + $excerpt = static::processLinkExcerpt($excerpt, $page, 'link'); + $excerpt['element']['attributes']['data-href'] = $original_href; + + $html = static::getHtmlFromExcerpt($excerpt); + + return $html; + } + + /** + * Get an Excerpt array from a chunk of HTML + * + * @param string $html Chunk of HTML + * @param string $tag A tag, for example `img` + * @return array|null returns nested array excerpt + */ + public static function getExcerptFromHtml($html, $tag) + { + $doc = new DOMDocument('1.0', 'UTF-8'); + $internalErrors = libxml_use_internal_errors(true); + $doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); + libxml_use_internal_errors($internalErrors); + + $elements = $doc->getElementsByTagName($tag); + $excerpt = null; + $inner = []; + + foreach ($elements as $element) { + $attributes = []; + foreach ($element->attributes as $name => $value) { + $attributes[$name] = $value->value; + } + $excerpt = [ + 'element' => [ + 'name' => $element->tagName, + 'attributes' => $attributes + ] + ]; + + foreach ($element->childNodes as $node) { + $inner[] = $doc->saveHTML($node); + } + + $excerpt = array_merge_recursive($excerpt, ['element' => ['text' => implode('', $inner)]]); + + + } + + return $excerpt; + } + + /** + * Rebuild HTML tag from an excerpt array + * + * @param array $excerpt + * @return string + */ + public static function getHtmlFromExcerpt($excerpt) + { + $element = $excerpt['element']; + $html = '<'.$element['name']; + + if (isset($element['attributes'])) { + foreach ($element['attributes'] as $name => $value) { + if ($value === null) { + continue; + } + $html .= ' '.$name.'="'.$value.'"'; + } + } + + if (isset($element['text'])) { + $html .= '>'; + $html .= is_array($element['text']) ? static::getHtmlFromExcerpt(['element' => $element['text']]) : $element['text']; + $html .= ''; + } else { + $html .= ' />'; + } + + return $html; + } + + /** + * Process a Link excerpt + * + * @param array $excerpt + * @param PageInterface|null $page Page, defaults to the current page object + * @param string $type + * @return mixed + */ + public static function processLinkExcerpt($excerpt, PageInterface $page = null, $type = 'link') + { + $excerpts = new ExcerptsObject($page); + + return $excerpts->processLinkExcerpt($excerpt, $type); + } + + /** + * Process an image excerpt + * + * @param array $excerpt + * @param PageInterface|null $page Page, defaults to the current page object + * @return array + */ + public static function processImageExcerpt(array $excerpt, PageInterface $page = null) + { + $excerpts = new ExcerptsObject($page); + + return $excerpts->processImageExcerpt($excerpt); + } + + /** + * Process media actions + * + * @param Medium $medium + * @param string|array $url + * @param PageInterface|null $page Page, defaults to the current page object + * @return Medium|Link + */ + public static function processMediaActions($medium, $url, PageInterface $page = null) + { + $excerpts = new ExcerptsObject($page); + + return $excerpts->processMediaActions($medium, $url); + } +} diff --git a/system/src/Grav/Common/Helpers/Exif.php b/system/src/Grav/Common/Helpers/Exif.php new file mode 100644 index 0000000..a458dcc --- /dev/null +++ b/system/src/Grav/Common/Helpers/Exif.php @@ -0,0 +1,48 @@ +get('system.media.auto_metadata_exif')) { + if (function_exists('exif_read_data') && class_exists(Reader::class)) { + $this->reader = Reader::factory(Reader::TYPE_NATIVE); + } else { + throw new RuntimeException('Please enable the Exif extension for PHP or disable Exif support in Grav system configuration'); + } + } + } + + /** + * @return Reader + */ + public function getReader() + { + return $this->reader; + } +} diff --git a/system/src/Grav/Common/Helpers/LogViewer.php b/system/src/Grav/Common/Helpers/LogViewer.php new file mode 100644 index 0000000..d657faa --- /dev/null +++ b/system/src/Grav/Common/Helpers/LogViewer.php @@ -0,0 +1,167 @@ +.*?)\] (?P\w+)\.(?P\w+): (?P.*[^ ]+) (?P[^ ]+) (?P[^ ]+)/'; + + /** + * Get the objects of a tailed file + * + * @param string $filepath + * @param int $lines + * @param bool $desc + * @return array + */ + public function objectTail($filepath, $lines = 1, $desc = true) + { + $data = $this->tail($filepath, $lines); + $tailed_log = $data ? explode(PHP_EOL, $data) : []; + $line_objects = []; + + foreach ($tailed_log as $line) { + $line_objects[] = $this->parse($line); + } + + return $desc ? $line_objects : array_reverse($line_objects); + } + + /** + * Optimized way to get just the last few entries of a log file + * + * @param string $filepath + * @param int $lines + * @return string|false + */ + public function tail($filepath, $lines = 1) + { + $f = $filepath ? @fopen($filepath, 'rb') : false; + if ($f === false) { + return false; + } + + $buffer = ($lines < 2 ? 64 : ($lines < 10 ? 512 : 4096)); + + fseek($f, -1, SEEK_END); + if (fread($f, 1) !== "\n") { + --$lines; + } + + // Start reading + $output = ''; + // While we would like more + while (ftell($f) > 0 && $lines >= 0) { + // Figure out how far back we should jump + $seek = min(ftell($f), $buffer); + // Do the jump (backwards, relative to where we are) + fseek($f, -$seek, SEEK_CUR); + // Read a chunk and prepend it to our output + $chunk = fread($f, $seek); + if ($chunk === false) { + throw new \RuntimeException('Cannot read file'); + } + $output = $chunk . $output; + // Jump back to where we started reading + fseek($f, -mb_strlen($chunk, '8bit'), SEEK_CUR); + // Decrease our line counter + $lines -= substr_count($chunk, "\n"); + } + // While we have too many lines + // (Because of buffer size we might have read too many) + while ($lines++ < 0) { + // Find first newline and remove all text before that + $output = substr($output, strpos($output, "\n") + 1); + } + // Close file and return + fclose($f); + + return trim($output); + } + + /** + * Helper class to get level color + * + * @param string $level + * @return string + */ + public static function levelColor($level) + { + $colors = [ + 'DEBUG' => 'green', + 'INFO' => 'cyan', + 'NOTICE' => 'yellow', + 'WARNING' => 'yellow', + 'ERROR' => 'red', + 'CRITICAL' => 'red', + 'ALERT' => 'red', + 'EMERGENCY' => 'magenta' + ]; + return $colors[$level] ?? 'white'; + } + + /** + * Parse a monolog row into array bits + * + * @param string $line + * @return array + */ + public function parse($line) + { + if (!is_string($line) || $line === '') { + return []; + } + + preg_match($this->pattern, $line, $data); + if (!isset($data['date'])) { + return []; + } + + preg_match('/(.*)- Trace:(.*)/', $data['message'], $matches); + if (is_array($matches) && isset($matches[1])) { + $data['message'] = trim($matches[1]); + $data['trace'] = trim($matches[2]); + } + + return [ + 'date' => DateTime::createFromFormat('Y-m-d H:i:s', $data['date']), + 'logger' => $data['logger'], + 'level' => $data['level'], + 'message' => $data['message'], + 'trace' => isset($data['trace']) ? self::parseTrace($data['trace']) : null, + 'context' => json_decode($data['context'], true), + 'extra' => json_decode($data['extra'], true) + ]; + } + + /** + * Parse text of trace into an array of lines + * + * @param string $trace + * @param int $rows + * @return array + */ + public static function parseTrace($trace, $rows = 10) + { + $lines = array_filter(preg_split('/#\d*/m', $trace)); + + return array_slice($lines, 0, $rows); + } +} diff --git a/system/src/Grav/Common/Helpers/Truncator.php b/system/src/Grav/Common/Helpers/Truncator.php new file mode 100644 index 0000000..0ccd034 --- /dev/null +++ b/system/src/Grav/Common/Helpers/Truncator.php @@ -0,0 +1,344 @@ +getElementsByTagName('div')->item(0); + $container = $container->parentNode->removeChild($container); + + // Iterate over words. + $words = new DOMWordsIterator($container); + $truncated = false; + foreach ($words as $word) { + // If we have exceeded the limit, we delete the remainder of the content. + if ($words->key() >= $limit) { + // Grab current position. + $currentWordPosition = $words->currentWordPosition(); + $curNode = $currentWordPosition[0]; + $offset = $currentWordPosition[1]; + $words = $currentWordPosition[2]; + + $curNode->nodeValue = substr( + $curNode->nodeValue, + 0, + $words[$offset][1] + strlen($words[$offset][0]) + ); + + self::removeProceedingNodes($curNode, $container); + + if (!empty($ellipsis)) { + self::insertEllipsis($curNode, $ellipsis); + } + + $truncated = true; + + break; + } + } + + // Return original HTML if not truncated. + if ($truncated) { + $html = self::getCleanedHtml($doc, $container); + } + + return $html; + } + + /** + * Safely truncates HTML by a given number of letters. + * + * @param string $html Input HTML. + * @param int $limit Limit to how many letters we preserve. + * @param string $ellipsis String to use as ellipsis (if any). + * @return string Safe truncated HTML. + */ + public static function truncateLetters($html, $limit = 0, $ellipsis = '') + { + if ($limit <= 0) { + return $html; + } + + $doc = self::htmlToDomDocument($html); + $container = $doc->getElementsByTagName('div')->item(0); + $container = $container->parentNode->removeChild($container); + + // Iterate over letters. + $letters = new DOMLettersIterator($container); + $truncated = false; + foreach ($letters as $letter) { + // If we have exceeded the limit, we want to delete the remainder of this document. + if ($letters->key() >= $limit) { + $currentText = $letters->currentTextPosition(); + $currentText[0]->nodeValue = mb_substr($currentText[0]->nodeValue, 0, $currentText[1] + 1); + self::removeProceedingNodes($currentText[0], $container); + + if (!empty($ellipsis)) { + self::insertEllipsis($currentText[0], $ellipsis); + } + + $truncated = true; + + break; + } + } + + // Return original HTML if not truncated. + if ($truncated) { + $html = self::getCleanedHtml($doc, $container); + } + + return $html; + } + + /** + * Builds a DOMDocument object from a string containing HTML. + * + * @param string $html HTML to load + * @return DOMDocument Returns a DOMDocument object. + */ + public static function htmlToDomDocument($html) + { + if (!$html) { + $html = ''; + } + + // Transform multibyte entities which otherwise display incorrectly. + $html = mb_encode_numericentity($html, [0x80, 0x10FFFF, 0, ~0], 'UTF-8'); + + // Internal errors enabled as HTML5 not fully supported. + libxml_use_internal_errors(true); + + // Instantiate new DOMDocument object, and then load in UTF-8 HTML. + $dom = new DOMDocument(); + $dom->encoding = 'UTF-8'; + $dom->loadHTML("
$html
"); + + return $dom; + } + + /** + * Removes all nodes after the current node. + * + * @param DOMNode|DOMElement $domNode + * @param DOMNode|DOMElement $topNode + * @return void + */ + private static function removeProceedingNodes($domNode, $topNode) + { + /** @var DOMNode|null $nextNode */ + $nextNode = $domNode->nextSibling; + + if ($nextNode !== null) { + self::removeProceedingNodes($nextNode, $topNode); + $domNode->parentNode->removeChild($nextNode); + } else { + //scan upwards till we find a sibling + $curNode = $domNode->parentNode; + while ($curNode !== $topNode) { + if ($curNode->nextSibling !== null) { + $curNode = $curNode->nextSibling; + self::removeProceedingNodes($curNode, $topNode); + $curNode->parentNode->removeChild($curNode); + break; + } + $curNode = $curNode->parentNode; + } + } + } + + /** + * Clean extra code + * + * @param DOMDocument $doc + * @param DOMNode $container + * @return string + */ + private static function getCleanedHTML(DOMDocument $doc, DOMNode $container) + { + while ($doc->firstChild) { + $doc->removeChild($doc->firstChild); + } + + while ($container->firstChild) { + $doc->appendChild($container->firstChild); + } + + return trim($doc->saveHTML()); + } + + /** + * Inserts an ellipsis + * + * @param DOMNode|DOMElement $domNode Element to insert after. + * @param string $ellipsis Text used to suffix our document. + * @return void + */ + private static function insertEllipsis($domNode, $ellipsis) + { + $avoid = array('a', 'strong', 'em', 'h1', 'h2', 'h3', 'h4', 'h5'); //html tags to avoid appending the ellipsis to + + if ($domNode->parentNode->parentNode !== null && in_array($domNode->parentNode->nodeName, $avoid, true)) { + // Append as text node to parent instead + $textNode = new DOMText($ellipsis); + + /** @var DOMNode|null $nextSibling */ + $nextSibling = $domNode->parentNode->parentNode->nextSibling; + if ($nextSibling) { + $domNode->parentNode->parentNode->insertBefore($textNode, $domNode->parentNode->parentNode->nextSibling); + } else { + $domNode->parentNode->parentNode->appendChild($textNode); + } + } else { + // Append to current node + $domNode->nodeValue = rtrim($domNode->nodeValue) . $ellipsis; + } + } + + /** + * @param string $text + * @param int $length + * @param string $ending + * @param bool $exact + * @param bool $considerHtml + * @return string + */ + public function truncate( + $text, + $length = 100, + $ending = '...', + $exact = false, + $considerHtml = true + ) { + if ($considerHtml) { + // if the plain text is shorter than the maximum length, return the whole text + if (strlen(preg_replace('/<.*?>/', '', $text)) <= $length) { + return $text; + } + + // splits all html-tags to scanable lines + preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER); + $total_length = strlen($ending); + $truncate = ''; + $open_tags = []; + + 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 + } elseif (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 + } elseif (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; + } + + $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 (false !== $spacepos) { + // ...and cut the text in this position + $truncate = substr($truncate, 0, $spacepos); + } + } + // add the defined ending to the text + $truncate .= $ending; + if (isset($open_tags)) { + // close all unclosed html-tags + foreach ($open_tags as $tag) { + $truncate .= ''; + } + } + + return $truncate; + } +} diff --git a/system/src/Grav/Common/Helpers/YamlLinter.php b/system/src/Grav/Common/Helpers/YamlLinter.php new file mode 100644 index 0000000..296c681 --- /dev/null +++ b/system/src/Grav/Common/Helpers/YamlLinter.php @@ -0,0 +1,122 @@ +get('system.pages.theme'); + $theme_path = 'themes://' . $current_theme . '/blueprints'; + + $locator->addPath('blueprints', '', [$theme_path]); + return static::recurseFolder('blueprints://'); + } + + /** + * @param string $path + * @param string $extensions + * @return array + */ + public static function recurseFolder($path, $extensions = '(md|yaml)') + { + $lint_errors = []; + + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + $flags = RecursiveDirectoryIterator::SKIP_DOTS | RecursiveDirectoryIterator::FOLLOW_SYMLINKS; + if ($locator->isStream($path)) { + $directory = $locator->getRecursiveIterator($path, $flags); + } else { + $directory = new RecursiveDirectoryIterator($path, $flags); + } + $recursive = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST); + $iterator = new RegexIterator($recursive, '/^.+\.'.$extensions.'$/ui'); + + /** @var RecursiveDirectoryIterator $file */ + foreach ($iterator as $filepath => $file) { + try { + Yaml::parse(static::extractYaml($filepath)); + } catch (Exception $e) { + $lint_errors[str_replace(GRAV_ROOT, '', $filepath)] = $e->getMessage(); + } + } + + return $lint_errors; + } + + /** + * @param string $path + * @return string + */ + protected static function extractYaml($path) + { + $extension = Utils::pathinfo($path, PATHINFO_EXTENSION); + if ($extension === 'md') { + $file = MarkdownFile::instance($path); + $contents = $file->frontmatter(); + $file->free(); + } else { + $contents = file_get_contents($path); + } + return $contents; + } +} diff --git a/system/src/Grav/Common/Inflector.php b/system/src/Grav/Common/Inflector.php new file mode 100644 index 0000000..e8f57bc --- /dev/null +++ b/system/src/Grav/Common/Inflector.php @@ -0,0 +1,363 @@ +isDebug()) { + static::$plural = $language->translate('GRAV.INFLECTOR_PLURALS', null, true); + static::$singular = $language->translate('GRAV.INFLECTOR_SINGULAR', null, true); + static::$uncountable = $language->translate('GRAV.INFLECTOR_UNCOUNTABLE', null, true); + static::$irregular = $language->translate('GRAV.INFLECTOR_IRREGULAR', null, true); + static::$ordinals = $language->translate('GRAV.INFLECTOR_ORDINALS', null, true); + } + } + } + + /** + * Pluralizes English nouns. + * + * @param string $word English noun to pluralize + * @param int $count The count + * @return string|false Plural noun + */ + public static function pluralize($word, $count = 2) + { + static::init(); + + if ((int)$count === 1) { + return $word; + } + + $lowercased_word = strtolower($word); + + if (is_array(static::$uncountable)) { + foreach (static::$uncountable as $_uncountable) { + if (substr($lowercased_word, -1 * strlen($_uncountable)) === $_uncountable) { + return $word; + } + } + } + + if (is_array(static::$irregular)) { + foreach (static::$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); + } + } + } + + if (is_array(static::$plural)) { + foreach (static::$plural as $rule => $replacement) { + if (preg_match($rule, $word)) { + return preg_replace($rule, $replacement, $word); + } + } + } + + return false; + } + + /** + * Singularizes English nouns. + * + * @param string $word English noun to singularize + * @param int $count + * + * @return string Singular noun. + */ + public static function singularize($word, $count = 1) + { + static::init(); + + if ((int)$count !== 1) { + return $word; + } + + $lowercased_word = strtolower($word); + + if (is_array(static::$uncountable)) { + foreach (static::$uncountable as $_uncountable) { + if (substr($lowercased_word, -1 * strlen($_uncountable)) === $_uncountable) { + return $word; + } + } + } + + if (is_array(static::$irregular)) { + foreach (static::$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); + } + } + } + + if (is_array(static::$singular)) { + foreach (static::$singular as $rule => $replacement) { + if (preg_match($rule, $word)) { + return preg_replace($rule, $replacement, $word); + } + } + } + + return $word; + } + + /** + * Converts an underscored or CamelCase word into a English + * sentence. + * + * The titleize public function converts text like "WelcomePage", + * "welcome_page" or "welcome page" to this "Welcome + * Page". + * If second parameter is set to 'first' it will only + * capitalize the first character of the title. + * + * @param string $word Word to format as tile + * @param string $uppercase If set to 'first' it will only uppercase the + * first character. Otherwise it will uppercase all + * the words in the title. + * + * @return string Text formatted as title + */ + public static function titleize($word, $uppercase = '') + { + $humanize_underscorize = static::humanize(static::underscorize($word)); + + if ($uppercase === 'first') { + $firstLetter = mb_strtoupper(mb_substr($humanize_underscorize, 0, 1, "UTF-8"), "UTF-8"); + return $firstLetter . mb_substr($humanize_underscorize, 1, mb_strlen($humanize_underscorize, "UTF-8"), "UTF-8"); + } else { + return mb_convert_case($humanize_underscorize, MB_CASE_TITLE, 'UTF-8'); + } + + } + + /** + * Returns given word as CamelCased + * + * Converts a word like "send_email" to "SendEmail". It + * will remove non alphanumeric character from the word, so + * "who's online" will be converted to "WhoSOnline" + * + * @see variablize + * + * @param string $word Word to convert to camel case + * @return string UpperCamelCasedWord + */ + public static function camelize($word) + { + return str_replace(' ', '', ucwords(preg_replace('/[^\p{L}^0-9]+/', ' ', $word))); + } + + /** + * Converts a word "into_it_s_underscored_version" + * + * Convert any "CamelCased" or "ordinary Word" into an + * "underscored_word". + * + * This can be really useful for creating friendly URLs. + * + * @param string $word Word to underscore + * @return string Underscored word + */ + public 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('/[^\p{L}^0-9]+/u', '_', $regex2); + + return strtolower($regex3); + } + + /** + * Converts a word "into-it-s-hyphenated-version" + * + * Convert any "CamelCased" or "ordinary Word" into an + * "hyphenated-word". + * + * This can be really useful for creating friendly URLs. + * + * @param string $word Word to hyphenate + * @return string hyphenized word + */ + public static function hyphenize($word) + { + $regex1 = preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1-\2', $word); + $regex2 = preg_replace('/([a-z])([A-Z])/', '\1-\2', $regex1); + $regex3 = preg_replace('/([0-9])([A-Z])/', '\1-\2', $regex2); + $regex4 = preg_replace('/[^\p{L}^0-9]+/', '-', $regex3); + + $regex4 = trim($regex4, '-'); + + return strtolower($regex4); + } + + /** + * Returns a human-readable string from $word + * + * Returns a human-readable string from $word, by replacing + * underscores with a space, and by upper-casing the initial + * character by default. + * + * If you need to uppercase all the words you just have to + * pass 'all' as a second parameter. + * + * @param string $word String to "humanize" + * @param string $uppercase If set to 'all' it will uppercase all the words + * instead of just the first one. + * + * @return string Human-readable word + */ + public 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" + * + * @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" + * + * @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::underscorize($class_name)); + } + + /** + * Converts a table name to its class name according to rails + * naming conventions. + * + * Converts "people" to "Person" + * + * @see tableize + * + * @param string $table_name Table name for getting related ClassName. + * @return string SingularClassName + */ + public 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 ... + * + * @param int $number Number to get its ordinal value + * @return string Ordinal representation of given string. + */ + public static function ordinalize($number) + { + static::init(); + + if (!is_array(static::$ordinals)) { + return (string)$number; + } + + if (in_array($number % 100, range(11, 13), true)) { + return $number . static::$ordinals['default']; + } + + switch ($number % 10) { + case 1: + return $number . static::$ordinals['first']; + case 2: + return $number . static::$ordinals['second']; + case 3: + return $number . static::$ordinals['third']; + default: + return $number . static::$ordinals['default']; + } + } + + /** + * Converts a number of days to a number of months + * + * @param int $days + * @return int + */ + public static function monthize($days) + { + $now = new DateTime(); + $end = new DateTime(); + + $duration = new DateInterval("P{$days}D"); + + $diff = $end->add($duration)->diff($now); + + // handle years + if ($diff->y > 0) { + $diff->m += 12 * $diff->y; + } + + return $diff->m; + } +} diff --git a/system/src/Grav/Common/Iterator.php b/system/src/Grav/Common/Iterator.php new file mode 100644 index 0000000..e5c1351 --- /dev/null +++ b/system/src/Grav/Common/Iterator.php @@ -0,0 +1,264 @@ +items[$key] ?? null; + } + + /** + * Clone the iterator. + */ + #[\ReturnTypeWillChange] + 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 + */ + #[\ReturnTypeWillChange] + public function __toString() + { + return implode(',', $this->items); + } + + /** + * Remove item from the list. + * + * @param string $key + * @return void + */ + public function remove($key) + { + $this->offsetUnset($key); + } + + /** + * Return previous item. + * + * @return mixed + */ + public function prev() + { + return prev($this->items); + } + + /** + * Return nth item. + * + * @param int $key + * @return mixed|bool + */ + public function nth($key) + { + $items = array_keys($this->items); + + return isset($items[$key]) ? $this->offsetGet($items[$key]) : false; + } + + /** + * Get the first item + * + * @return mixed + */ + public function first() + { + $items = array_keys($this->items); + + return $this->offsetGet(array_shift($items)); + } + + /** + * Get the last item + * + * @return mixed + */ + public function last() + { + $items = array_keys($this->items); + + return $this->offsetGet(array_pop($items)); + } + + /** + * Reverse the Iterator + * + * @return $this + */ + public function reverse() + { + $this->items = array_reverse($this->items); + + return $this; + } + + /** + * @param mixed $needle Searched value. + * + * @return string|int|false Key if found, otherwise false. + */ + public function indexOf($needle) + { + foreach (array_values($this->items) as $key => $value) { + if ($value === $needle) { + return $key; + } + } + + return false; + } + + /** + * Shuffle items. + * + * @return $this + */ + public function shuffle() + { + $keys = array_keys($this->items); + shuffle($keys); + + $new = []; + foreach ($keys as $key) { + $new[$key] = $this->items[$key]; + } + + $this->items = $new; + + return $this; + } + + /** + * Slice the list. + * + * @param int $offset + * @param int|null $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) + { + $count = count($this->items); + if ($num > $count) { + $num = $count; + } + + $this->items = array_intersect_key($this->items, array_flip((array)array_rand($this->items, $num))); + + return $this; + } + + /** + * Append new elements to the list. + * + * @param array|Iterator $items Items to be appended. Existing keys will be overridden with the new values. + * @return $this + */ + public function append($items) + { + if ($items instanceof static) { + $items = $items->toArray(); + } + $this->items = array_merge($this->items, (array)$items); + + return $this; + } + + /** + * Filter elements from the list + * + * @param callable|null $callback A function the receives ($value, $key) and must return a boolean to indicate + * filter status + * + * @return $this + */ + public function filter(callable $callback = null) + { + foreach ($this->items as $key => $value) { + if ((!$callback && !(bool)$value) || ($callback && !$callback($value, $key))) { + unset($this->items[$key]); + } + } + + return $this; + } + + + /** + * Sorts elements from the list and returns a copy of the list in the proper order + * + * @param callable|null $callback + * @param bool $desc + * @return $this|array + * + */ + public function sort(callable $callback = null, $desc = false) + { + if (!$callback || !is_callable($callback)) { + return $this; + } + + $items = $this->items; + uasort($items, $callback); + + return !$desc ? $items : array_reverse($items, true); + } +} diff --git a/system/src/Grav/Common/Language/Language.php b/system/src/Grav/Common/Language/Language.php new file mode 100644 index 0000000..66a5bab --- /dev/null +++ b/system/src/Grav/Common/Language/Language.php @@ -0,0 +1,663 @@ +grav = $grav; + $this->config = $grav['config']; + + $languages = $this->config->get('system.languages.supported', []); + foreach ($languages as &$language) { + $language = (string)$language; + } + unset($language); + + $this->languages = $languages; + + $this->init(); + } + + /** + * Initialize the default and enabled languages + * + * @return void + */ + public function init() + { + $default = $this->config->get('system.languages.default_lang'); + if (null !== $default) { + $default = (string)$default; + } + + // Note that reset returns false on empty languages. + $this->default = $default ?? reset($this->languages); + + $this->resetFallbackPageExtensions(); + + if (empty($this->languages)) { + // If no languages are set, turn of multi-language support. + $this->enabled = false; + } elseif ($default && !in_array($default, $this->languages, true)) { + // If default language isn't in the language list, we need to add it. + array_unshift($this->languages, $default); + } + } + + /** + * Ensure that languages are enabled + * + * @return bool + */ + public function enabled() + { + return $this->enabled; + } + + /** + * Returns true if language debugging is turned on. + * + * @return bool + */ + public function isDebug(): bool + { + return !$this->config->get('system.languages.translations', true); + } + + /** + * Gets the array of supported languages + * + * @return array + */ + public function getLanguages() + { + return $this->languages; + } + + /** + * Sets the current supported languages manually + * + * @param array $langs + * @return void + */ + public function setLanguages($langs) + { + $this->languages = $langs; + + $this->init(); + } + + /** + * Gets a pipe-separated string of available languages + * + * @param string|null $delimiter Delimiter to be quoted. + * @return string + */ + public function getAvailable($delimiter = null) + { + $languagesArray = $this->languages; //Make local copy + + $languagesArray = array_map(static function ($value) use ($delimiter) { + return preg_quote($value, $delimiter); + }, $languagesArray); + + sort($languagesArray); + + return implode('|', array_reverse($languagesArray)); + } + + /** + * Gets language, active if set, else default + * + * @return string|false + */ + public function getLanguage() + { + return $this->active ?: $this->default; + } + + /** + * Gets current default language + * + * @return string|false + */ + public function getDefault() + { + return $this->default; + } + + /** + * Sets default language manually + * + * @param string $lang + * @return string|bool + */ + public function setDefault($lang) + { + $lang = (string)$lang; + if ($this->validate($lang)) { + $this->default = $lang; + + return $lang; + } + + return false; + } + + /** + * Gets current active language + * + * @return string|false + */ + public function getActive() + { + return $this->active; + } + + /** + * Sets active language manually + * + * @param string|false $lang + * @return string|false + */ + public function setActive($lang) + { + $lang = (string)$lang; + if ($this->validate($lang)) { + /** @var Debugger $debugger */ + $debugger = $this->grav['debugger']; + $debugger->addMessage('Active language set to ' . $lang, 'debug'); + + $this->active = $lang; + + return $lang; + } + + return false; + } + + /** + * Sets the active language based on the first part of the URL + * + * @param string $uri + * @return string + */ + public function setActiveFromUri($uri) + { + $regex = '/(^\/(' . $this->getAvailable() . '))(?:\/|\?|$)/i'; + + // if languages set + if ($this->enabled()) { + // Try setting language from prefix of URL (/en/blah/blah). + if (preg_match($regex, $uri, $matches)) { + $this->lang_in_url = true; + $this->setActive($matches[2]); + $uri = preg_replace("/\\" . $matches[1] . '/', '', $uri, 1); + + // Store in session if language is different. + if (isset($this->grav['session']) && $this->grav['session']->isStarted() + && $this->config->get('system.languages.session_store_active', true) + && $this->grav['session']->active_language != $this->active + ) { + $this->grav['session']->active_language = $this->active; + } + } else { + // Try getting language from the session, else no active. + if (isset($this->grav['session']) && $this->grav['session']->isStarted() && + $this->config->get('system.languages.session_store_active', true)) { + $this->setActive($this->grav['session']->active_language ?: null); + } + // if still null, try from http_accept_language header + if ($this->active === null && + $this->config->get('system.languages.http_accept_language') && + $accept = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? false) { + $negotiator = new LanguageNegotiator(); + $best_language = $negotiator->getBest($accept, $this->languages); + + if ($best_language instanceof AcceptLanguage) { + $this->setActive($best_language->getType()); + } else { + $this->setActive($this->getDefault()); + } + } + } + } + + return $uri; + } + + /** + * Get a URL prefix based on configuration + * + * @param string|null $lang + * @return string + */ + public function getLanguageURLPrefix($lang = null) + { + if (!$this->enabled()) { + return ''; + } + + // if active lang is not passed in, use current active + if (!$lang) { + $lang = $this->getLanguage(); + } + + return $this->isIncludeDefaultLanguage($lang) ? '/' . $lang : ''; + } + + /** + * Test to see if language is default and language should be included in the URL + * + * @param string|null $lang + * @return bool + */ + public function isIncludeDefaultLanguage($lang = null) + { + if (!$this->enabled()) { + return false; + } + + // if active lang is not passed in, use current active + if (!$lang) { + $lang = $this->getLanguage(); + } + + return !($this->default === $lang && $this->config->get('system.languages.include_default_lang') === false); + } + + /** + * Simple getter to tell if a language was found in the URL + * + * @return bool + */ + public function isLanguageInUrl() + { + return (bool) $this->lang_in_url; + } + + /** + * Get full list of used language page extensions: [''=>'.md', 'en'=>'.en.md', ...] + * + * @param string|null $fileExtension + * @return array + */ + public function getPageExtensions($fileExtension = null) + { + $fileExtension = $fileExtension ?: CONTENT_EXT; + + if (!isset($this->fallback_extensions[$fileExtension])) { + $extensions[''] = $fileExtension; + foreach ($this->languages as $code) { + $extensions[$code] = ".{$code}{$fileExtension}"; + } + + $this->fallback_extensions[$fileExtension] = $extensions; + } + + return $this->fallback_extensions[$fileExtension]; + } + + /** + * Gets an array of valid extensions with active first, then fallback extensions + * + * @param string|null $fileExtension + * @param string|null $languageCode + * @param bool $assoc Return values in ['en' => '.en.md', ...] format. + * @return array Key is the language code, value is the file extension to be used. + */ + public function getFallbackPageExtensions(string $fileExtension = null, string $languageCode = null, bool $assoc = false) + { + $fileExtension = $fileExtension ?: CONTENT_EXT; + $key = $fileExtension . '-' . ($languageCode ?? 'default') . '-' . (int)$assoc; + + if (!isset($this->fallback_extensions[$key])) { + $all = $this->getPageExtensions($fileExtension); + $list = []; + $fallback = $this->getFallbackLanguages($languageCode, true); + foreach ($fallback as $code) { + $ext = $all[$code] ?? null; + if (null !== $ext) { + $list[$code] = $ext; + } + } + if (!$assoc) { + $list = array_values($list); + } + + $this->fallback_extensions[$key] = $list; + } + + return $this->fallback_extensions[$key]; + } + + /** + * Resets the fallback_languages value. + * + * Useful to re-initialize the pages and change site language at runtime, example: + * + * ``` + * $this->grav['language']->setActive('it'); + * $this->grav['language']->resetFallbackPageExtensions(); + * $this->grav['pages']->init(); + * ``` + * + * @return void + */ + public function resetFallbackPageExtensions() + { + $this->fallback_languages = []; + $this->fallback_extensions = []; + $this->page_extensions = []; + } + + /** + * Gets an array of languages with active first, then fallback languages. + * + * + * @param string|null $languageCode + * @param bool $includeDefault If true, list contains '', which can be used for default + * @return array + */ + public function getFallbackLanguages(string $languageCode = null, bool $includeDefault = false) + { + // Handle default. + if ($languageCode === '' || !$this->enabled()) { + return ['']; + } + + $default = $this->getDefault() ?? 'en'; + $active = $languageCode ?? $this->getActive() ?? $default; + $key = $active . '-' . (int)$includeDefault; + + if (!isset($this->fallback_languages[$key])) { + $fallback = $this->config->get('system.languages.content_fallback.' . $active); + $fallback_languages = []; + + if (null === $fallback && $this->config->get('system.languages.pages_fallback_only', false)) { + user_error('Configuration option `system.languages.pages_fallback_only` is deprecated since Grav 1.7, use `system.languages.content_fallback` instead', E_USER_DEPRECATED); + + // Special fallback list returns itself and all the previous items in reverse order: + // active: 'v2', languages: ['v1', 'v2', 'v3', 'v4'] => ['v2', 'v1', ''] + if ($includeDefault) { + $fallback_languages[''] = ''; + } + foreach ($this->languages as $code) { + $fallback_languages[$code] = $code; + if ($code === $active) { + break; + } + } + $fallback_languages = array_reverse($fallback_languages); + } else { + if (null === $fallback) { + $fallback = [$default]; + } elseif (!is_array($fallback)) { + $fallback = is_string($fallback) && $fallback !== '' ? explode(',', $fallback) : []; + } + array_unshift($fallback, $active); + $fallback = array_unique($fallback); + + foreach ($fallback as $code) { + // Default fallback list has active language followed by default language and extensionless file: + // active: 'fi', default: 'en', languages: ['sv', 'en', 'de', 'fi'] => ['fi', 'en', ''] + $fallback_languages[$code] = $code; + if ($includeDefault && $code === $default) { + $fallback_languages[''] = ''; + } + } + } + + $fallback_languages = array_values($fallback_languages); + + $this->fallback_languages[$key] = $fallback_languages; + } + + return $this->fallback_languages[$key]; + } + + /** + * Ensures the language is valid and supported + * + * @param string $lang + * @return bool + */ + public function validate($lang) + { + return in_array($lang, $this->languages, true); + } + + /** + * Translate a key and possibly arguments into a string using current lang and fallbacks + * + * @param string|array $args The first argument is the lookup key value + * Other arguments can be passed and replaced in the translation with sprintf syntax + * @param array|null $languages + * @param bool $array_support + * @param bool $html_out + * @return string|string[] + */ + public function translate($args, array $languages = null, $array_support = false, $html_out = false) + { + if (is_array($args)) { + $lookup = array_shift($args); + } else { + $lookup = $args; + $args = []; + } + + if (!$this->isDebug()) { + if ($lookup && $this->enabled() && empty($languages)) { + $languages = $this->getTranslatedLanguages(); + } + + $languages = $languages ?: ['en']; + + foreach ((array)$languages as $lang) { + $translation = $this->getTranslation($lang, $lookup, $array_support); + + if ($translation) { + if (is_string($translation) && count($args) >= 1) { + return vsprintf($translation, $args); + } + + return $translation; + } + } + } elseif ($array_support) { + return [$lookup]; + } + + if ($html_out) { + return '' . $lookup . ''; + } + + return $lookup; + } + + /** + * Translate Array + * + * @param string $key + * @param string $index + * @param array|null $languages + * @param bool $html_out + * @return string + */ + public function translateArray($key, $index, $languages = null, $html_out = false) + { + if ($this->isDebug()) { + return $key . '[' . $index . ']'; + } + + if ($key && empty($languages) && $this->enabled()) { + $languages = $this->getTranslatedLanguages(); + } + + $languages = $languages ?: ['en']; + + foreach ((array)$languages as $lang) { + $translation_array = (array)Grav::instance()['languages']->get($lang . '.' . $key, null); + if ($translation_array && array_key_exists($index, $translation_array)) { + return $translation_array[$index]; + } + } + + if ($html_out) { + return '' . $key . '[' . $index . ']'; + } + + return $key . '[' . $index . ']'; + } + + /** + * Lookup the translation text for a given lang and key + * + * @param string $lang lang code + * @param string $key key to lookup with + * @param bool $array_support + * @return string|string[] + */ + public function getTranslation($lang, $key, $array_support = false) + { + if ($this->isDebug()) { + return $key; + } + + $translation = Grav::instance()['languages']->get($lang . '.' . $key, null); + if (!$array_support && is_array($translation)) { + return (string)array_shift($translation); + } + + return $translation; + } + + /** + * Get the browser accepted languages + * + * @param array $accept_langs + * @return array + * @deprecated 1.6 No longer used - using content negotiation. + */ + public function getBrowserLanguages($accept_langs = []) + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, no longer used', E_USER_DEPRECATED); + + if (empty($this->http_accept_language)) { + if (empty($accept_langs) && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { + $accept_langs = $_SERVER['HTTP_ACCEPT_LANGUAGE']; + } else { + return $accept_langs; + } + + $langs = []; + + foreach (explode(',', $accept_langs) as $k => $pref) { + // split $pref again by ';q=' + // and decorate the language entries by inverted position + if (false !== ($i = strpos($pref, ';q='))) { + $langs[substr($pref, 0, $i)] = [(float)substr($pref, $i + 3), -$k]; + } else { + $langs[$pref] = [1, -$k]; + } + } + arsort($langs); + + // no need to undecorate, because we're only interested in the keys + $this->http_accept_language = array_keys($langs); + } + return $this->http_accept_language; + } + + /** + * Accessible wrapper to LanguageCodes + * + * @param string $code + * @param string $type + * @return string|false + */ + public function getLanguageCode($code, $type = 'name') + { + return LanguageCodes::get($code, $type); + } + + /** + * @return array + */ + #[\ReturnTypeWillChange] + public function __debugInfo() + { + $vars = get_object_vars($this); + unset($vars['grav'], $vars['config']); + + return $vars; + } + + /** + * @return array + */ + protected function getTranslatedLanguages(): array + { + if ($this->config->get('system.languages.translations_fallback', true)) { + $languages = $this->getFallbackLanguages(); + } else { + $languages = [$this->getLanguage()]; + } + + $languages[] = 'en'; + + return array_values(array_unique($languages)); + } +} diff --git a/system/src/Grav/Common/Language/LanguageCodes.php b/system/src/Grav/Common/Language/LanguageCodes.php new file mode 100644 index 0000000..fc82cd1 --- /dev/null +++ b/system/src/Grav/Common/Language/LanguageCodes.php @@ -0,0 +1,246 @@ + [ 'name' => 'Afrikaans', 'nativeName' => 'Afrikaans' ], + 'ak' => [ 'name' => 'Akan', 'nativeName' => 'Akan' ], // unverified native name + 'ast' => [ 'name' => 'Asturian', 'nativeName' => 'Asturianu' ], + 'ar' => [ 'name' => 'Arabic', 'nativeName' => 'عربي', 'orientation' => 'rtl'], + 'as' => [ 'name' => 'Assamese', 'nativeName' => 'অসমীয়া' ], + 'be' => [ 'name' => 'Belarusian', 'nativeName' => 'Беларуская' ], + 'bg' => [ 'name' => 'Bulgarian', 'nativeName' => 'Български' ], + 'bn' => [ 'name' => 'Bengali', 'nativeName' => 'বাংলা' ], + 'bn-BD' => [ 'name' => 'Bengali (Bangladesh)', 'nativeName' => 'বাংলা (বাংলাদেশ)' ], + 'bn-IN' => [ 'name' => 'Bengali (India)', 'nativeName' => 'বাংলা (ভারত)' ], + 'br' => [ 'name' => 'Breton', 'nativeName' => 'Brezhoneg' ], + 'bs' => [ 'name' => 'Bosnian', 'nativeName' => 'Bosanski' ], + 'ca' => [ 'name' => 'Catalan', 'nativeName' => 'Català' ], + 'ca-valencia'=> [ 'name' => 'Catalan (Valencian)', 'nativeName' => 'Català (valencià)' ], // not iso-639-1. a=l10n-drivers + 'cs' => [ 'name' => 'Czech', 'nativeName' => 'Čeština' ], + 'cy' => [ 'name' => 'Welsh', 'nativeName' => 'Cymraeg' ], + 'da' => [ 'name' => 'Danish', 'nativeName' => 'Dansk' ], + 'de' => [ 'name' => 'German', 'nativeName' => 'Deutsch' ], + 'de-AT' => [ 'name' => 'German (Austria)', 'nativeName' => 'Deutsch (Österreich)' ], + 'de-CH' => [ 'name' => 'German (Switzerland)', 'nativeName' => 'Deutsch (Schweiz)' ], + 'de-DE' => [ 'name' => 'German (Germany)', 'nativeName' => 'Deutsch (Deutschland)' ], + 'dsb' => [ 'name' => 'Lower Sorbian', 'nativeName' => 'Dolnoserbšćina' ], // iso-639-2 + 'el' => [ 'name' => 'Greek', 'nativeName' => 'Ελληνικά' ], + 'en' => [ 'name' => 'English', 'nativeName' => 'English' ], + 'en-AU' => [ 'name' => 'English (Australian)', 'nativeName' => 'English (Australian)' ], + 'en-CA' => [ 'name' => 'English (Canadian)', 'nativeName' => 'English (Canadian)' ], + 'en-GB' => [ 'name' => 'English (British)', 'nativeName' => 'English (British)' ], + 'en-NZ' => [ 'name' => 'English (New Zealand)', 'nativeName' => 'English (New Zealand)' ], + 'en-US' => [ 'name' => 'English (US)', 'nativeName' => 'English (US)' ], + 'en-ZA' => [ 'name' => 'English (South African)', 'nativeName' => 'English (South African)' ], + 'eo' => [ 'name' => 'Esperanto', 'nativeName' => 'Esperanto' ], + 'es' => [ 'name' => 'Spanish', 'nativeName' => 'Español' ], + 'es-AR' => [ 'name' => 'Spanish (Argentina)', 'nativeName' => 'Español (de Argentina)' ], + 'es-CL' => [ 'name' => 'Spanish (Chile)', 'nativeName' => 'Español (de Chile)' ], + 'es-ES' => [ 'name' => 'Spanish (Spain)', 'nativeName' => 'Español (de España)' ], + 'es-MX' => [ 'name' => 'Spanish (Mexico)', 'nativeName' => 'Español (de México)' ], + 'et' => [ 'name' => 'Estonian', 'nativeName' => 'Eesti keel' ], + 'eu' => [ 'name' => 'Basque', 'nativeName' => 'Euskara' ], + 'fa' => [ 'name' => 'Persian', 'nativeName' => 'فارسی' , 'orientation' => 'rtl' ], + 'fi' => [ 'name' => 'Finnish', 'nativeName' => 'Suomi' ], + 'fj-FJ' => [ 'name' => 'Fijian', 'nativeName' => 'Vosa vaka-Viti' ], + 'fr' => [ 'name' => 'French', 'nativeName' => 'Français' ], + 'fr-CA' => [ 'name' => 'French (Canada)', 'nativeName' => 'Français (Canada)' ], + 'fr-FR' => [ 'name' => 'French (France)', 'nativeName' => 'Français (France)' ], + 'fur' => [ 'name' => 'Friulian', 'nativeName' => 'Furlan' ], + 'fur-IT' => [ 'name' => 'Friulian', 'nativeName' => 'Furlan' ], + 'fy' => [ 'name' => 'Frisian', 'nativeName' => 'Frysk' ], + 'fy-NL' => [ 'name' => 'Frisian', 'nativeName' => 'Frysk' ], + 'ga' => [ 'name' => 'Irish', 'nativeName' => 'Gaeilge' ], + 'ga-IE' => [ 'name' => 'Irish (Ireland)', 'nativeName' => 'Gaeilge (Éire)' ], + 'gd' => [ 'name' => 'Gaelic (Scotland)', 'nativeName' => 'Gàidhlig' ], + 'gl' => [ 'name' => 'Galician', 'nativeName' => 'Galego' ], + 'gu' => [ 'name' => 'Gujarati', 'nativeName' => 'ગુજરાતી' ], + 'gu-IN' => [ 'name' => 'Gujarati', 'nativeName' => 'ગુજરાતી' ], + 'he' => [ 'name' => 'Hebrew', 'nativeName' => 'עברית', 'orientation' => 'rtl' ], + 'hi' => [ 'name' => 'Hindi', 'nativeName' => 'हिन्दी' ], + 'hi-IN' => [ 'name' => 'Hindi (India)', 'nativeName' => 'हिन्दी (भारत)' ], + 'hr' => [ 'name' => 'Croatian', 'nativeName' => 'Hrvatski' ], + 'hsb' => [ 'name' => 'Upper Sorbian', 'nativeName' => 'Hornjoserbsce' ], + 'hu' => [ 'name' => 'Hungarian', 'nativeName' => 'Magyar' ], + 'hy' => [ 'name' => 'Armenian', 'nativeName' => 'Հայերեն' ], + 'hy-AM' => [ 'name' => 'Armenian', 'nativeName' => 'Հայերեն' ], + 'id' => [ 'name' => 'Indonesian', 'nativeName' => 'Bahasa Indonesia' ], + 'is' => [ 'name' => 'Icelandic', 'nativeName' => 'íslenska' ], + 'it' => [ 'name' => 'Italian', 'nativeName' => 'Italiano' ], + 'ja' => [ 'name' => 'Japanese', 'nativeName' => '日本語' ], + 'ja-JP' => [ 'name' => 'Japanese', 'nativeName' => '日本語' ], // not iso-639-1 + 'ka' => [ 'name' => 'Georgian', 'nativeName' => 'ქართული' ], + 'kk' => [ 'name' => 'Kazakh', 'nativeName' => 'Қазақ' ], + 'km' => [ 'name' => 'Khmer', 'nativeName' => 'Khmer' ], + 'kn' => [ 'name' => 'Kannada', 'nativeName' => 'ಕನ್ನಡ' ], + 'ko' => [ 'name' => 'Korean', 'nativeName' => '한국어' ], + 'ku' => [ 'name' => 'Kurdish', 'nativeName' => 'Kurdî' ], + 'la' => [ 'name' => 'Latin', 'nativeName' => 'Latina' ], + 'lb' => [ 'name' => 'Luxembourgish', 'nativeName' => 'Lëtzebuergesch' ], + 'lg' => [ 'name' => 'Luganda', 'nativeName' => 'Luganda' ], + 'lo' => [ 'name' => 'Lao', 'nativeName' => 'Lao' ], + 'lt' => [ 'name' => 'Lithuanian', 'nativeName' => 'Lietuvių' ], + 'lv' => [ 'name' => 'Latvian', 'nativeName' => 'Latviešu' ], + 'mai' => [ 'name' => 'Maithili', 'nativeName' => 'मैथिली মৈথিলী' ], + 'mg' => [ 'name' => 'Malagasy', 'nativeName' => 'Malagasy' ], + 'mi' => [ 'name' => 'Maori (Aotearoa)', 'nativeName' => 'Māori (Aotearoa)' ], + 'mk' => [ 'name' => 'Macedonian', 'nativeName' => 'Македонски' ], + 'ml' => [ 'name' => 'Malayalam', 'nativeName' => 'മലയാളം' ], + 'mn' => [ 'name' => 'Mongolian', 'nativeName' => 'Монгол' ], + 'mr' => [ 'name' => 'Marathi', 'nativeName' => 'मराठी' ], + 'my' => [ 'name' => 'Myanmar (Burmese)', 'nativeName' => 'ဗမာी' ], + 'no' => [ 'name' => 'Norwegian', 'nativeName' => 'Norsk' ], + 'nb' => [ 'name' => 'Norwegian', 'nativeName' => 'Norsk' ], + 'nb-NO' => [ 'name' => 'Norwegian (Bokmål)', 'nativeName' => 'Norsk bokmål' ], + 'ne-NP' => [ 'name' => 'Nepali', 'nativeName' => 'नेपाली' ], + 'nn-NO' => [ 'name' => 'Norwegian (Nynorsk)', 'nativeName' => 'Norsk nynorsk' ], + 'nl' => [ 'name' => 'Dutch', 'nativeName' => 'Nederlands' ], + 'nr' => [ 'name' => 'Ndebele, South', 'nativeName' => 'IsiNdebele' ], + 'nso' => [ 'name' => 'Northern Sotho', 'nativeName' => 'Sepedi' ], + 'oc' => [ 'name' => 'Occitan (Lengadocian)', 'nativeName' => 'Occitan (lengadocian)' ], + 'or' => [ 'name' => 'Oriya', 'nativeName' => 'ଓଡ଼ିଆ' ], + 'pa' => [ 'name' => 'Punjabi', 'nativeName' => 'ਪੰਜਾਬੀ' ], + 'pa-IN' => [ 'name' => 'Punjabi', 'nativeName' => 'ਪੰਜਾਬੀ' ], + 'pl' => [ 'name' => 'Polish', 'nativeName' => 'Polski' ], + 'pt' => [ 'name' => 'Portuguese', 'nativeName' => 'Português' ], + 'pt-BR' => [ 'name' => 'Portuguese (Brazilian)', 'nativeName' => 'Português (do Brasil)' ], + 'pt-PT' => [ 'name' => 'Portuguese (Portugal)', 'nativeName' => 'Português (Europeu)' ], + 'ro' => [ 'name' => 'Romanian', 'nativeName' => 'Română' ], + 'rm' => [ 'name' => 'Romansh', 'nativeName' => 'Rumantsch' ], + 'ru' => [ 'name' => 'Russian', 'nativeName' => 'Русский' ], + 'rw' => [ 'name' => 'Kinyarwanda', 'nativeName' => 'Ikinyarwanda' ], + 'si' => [ 'name' => 'Sinhala', 'nativeName' => 'සිංහල' ], + 'sk' => [ 'name' => 'Slovak', 'nativeName' => 'Slovenčina' ], + 'sl' => [ 'name' => 'Slovenian', 'nativeName' => 'Slovensko' ], + 'son' => [ 'name' => 'Songhai', 'nativeName' => 'Soŋay' ], + 'sq' => [ 'name' => 'Albanian', 'nativeName' => 'Shqip' ], + 'sr' => [ 'name' => 'Serbian', 'nativeName' => 'Српски' ], + 'sr-Latn' => [ 'name' => 'Serbian', 'nativeName' => 'Srpski' ], // follows RFC 4646 + 'ss' => [ 'name' => 'Siswati', 'nativeName' => 'siSwati' ], + 'st' => [ 'name' => 'Southern Sotho', 'nativeName' => 'Sesotho' ], + 'sv' => [ 'name' => 'Swedish', 'nativeName' => 'Svenska' ], + 'sv-SE' => [ 'name' => 'Swedish', 'nativeName' => 'Svenska' ], + 'sw' => [ 'name' => 'Swahili', 'nativeName' => 'Swahili' ], + 'ta' => [ 'name' => 'Tamil', 'nativeName' => 'தமிழ்' ], + 'ta-IN' => [ 'name' => 'Tamil (India)', 'nativeName' => 'தமிழ் (இந்தியா)' ], + 'ta-LK' => [ 'name' => 'Tamil (Sri Lanka)', 'nativeName' => 'தமிழ் (இலங்கை)' ], + 'te' => [ 'name' => 'Telugu', 'nativeName' => 'తెలుగు' ], + 'th' => [ 'name' => 'Thai', 'nativeName' => 'ไทย' ], + 'tlh' => [ 'name' => 'Klingon', 'nativeName' => 'Klingon' ], + 'tn' => [ 'name' => 'Tswana', 'nativeName' => 'Setswana' ], + 'tr' => [ 'name' => 'Turkish', 'nativeName' => 'Türkçe' ], + 'ts' => [ 'name' => 'Tsonga', 'nativeName' => 'Xitsonga' ], + 'tt' => [ 'name' => 'Tatar', 'nativeName' => 'Tatarça' ], + 'tt-RU' => [ 'name' => 'Tatar', 'nativeName' => 'Tatarça' ], + 'uk' => [ 'name' => 'Ukrainian', 'nativeName' => 'Українська' ], + 'ur' => [ 'name' => 'Urdu', 'nativeName' => 'اُردو', 'orientation' => 'rtl' ], + 've' => [ 'name' => 'Venda', 'nativeName' => 'Tshivenḓa' ], + 'vi' => [ 'name' => 'Vietnamese', 'nativeName' => 'Tiếng Việt' ], + 'wo' => [ 'name' => 'Wolof', 'nativeName' => 'Wolof' ], + 'xh' => [ 'name' => 'Xhosa', 'nativeName' => 'isiXhosa' ], + 'yi' => [ 'name' => 'Yiddish', 'nativeName' => 'ייִדיש', 'orientation' => 'rtl' ], + 'ydd' => [ 'name' => 'Yiddish', 'nativeName' => 'ייִדיש', 'orientation' => 'rtl' ], + 'zh' => [ 'name' => 'Chinese (Simplified)', 'nativeName' => '中文 (简体)' ], + 'zh-CN' => [ 'name' => 'Chinese (Simplified)', 'nativeName' => '中文 (简体)' ], + 'zh-TW' => [ 'name' => 'Chinese (Traditional)', 'nativeName' => '正體中文 (繁體)' ], + 'zu' => [ 'name' => 'Zulu', 'nativeName' => 'isiZulu' ] + ]; + + /** + * @param string $code + * @return string|false + */ + public static function getName($code) + { + return static::get($code, 'name'); + } + + /** + * @param string $code + * @return string|false + */ + public static function getNativeName($code) + { + if (isset(static::$codes[$code])) { + return static::get($code, 'nativeName'); + } + + if (preg_match('/[a-zA-Z]{2}-[a-zA-Z]{2}/', $code)) { + return static::get(substr($code, 0, 2), 'nativeName') . ' (' . substr($code, -2) . ')'; + } + + return $code; + } + + /** + * @param string $code + * @return string + */ + public static function getOrientation($code) + { + return static::$codes[$code]['orientation'] ?? 'ltr'; + } + + /** + * @param string $code + * @return bool + */ + public static function isRtl($code) + { + return static::getOrientation($code) === 'rtl'; + } + + /** + * @param array $keys + * @return array + */ + public static function getNames(array $keys) + { + $results = []; + foreach ($keys as $key) { + if (isset(static::$codes[$key])) { + $results[$key] = static::$codes[$key]; + } + } + return $results; + } + + /** + * @param string $code + * @param string $type + * @return string|false + */ + public static function get($code, $type) + { + return static::$codes[$code][$type] ?? false; + } + + /** + * @param bool $native + * @return array + */ + public static function getList($native = true) + { + $list = []; + foreach (static::$codes as $key => $names) { + $list[$key] = $native ? $names['nativeName'] : $names['name']; + } + + return $list; + } +} diff --git a/system/src/Grav/Common/Markdown/Parsedown.php b/system/src/Grav/Common/Markdown/Parsedown.php new file mode 100644 index 0000000..164f264 --- /dev/null +++ b/system/src/Grav/Common/Markdown/Parsedown.php @@ -0,0 +1,43 @@ + $defaults]; + } + $excerpts = new Excerpts($excerpts, $defaults); + user_error(__CLASS__ . '::' . __FUNCTION__ . '($page, $defaults) is deprecated since Grav 1.6.10, use new ' . __CLASS__ . '(new ' . Excerpts::class . '($page, [\'markdown\' => $defaults])) instead.', E_USER_DEPRECATED); + } + + $this->init($excerpts, $defaults); + } +} diff --git a/system/src/Grav/Common/Markdown/ParsedownExtra.php b/system/src/Grav/Common/Markdown/ParsedownExtra.php new file mode 100644 index 0000000..d9259e8 --- /dev/null +++ b/system/src/Grav/Common/Markdown/ParsedownExtra.php @@ -0,0 +1,46 @@ + $defaults]; + } + $excerpts = new Excerpts($excerpts, $defaults); + user_error(__CLASS__ . '::' . __FUNCTION__ . '($page, $defaults) is deprecated since Grav 1.6.10, use new ' . __CLASS__ . '(new ' . Excerpts::class . '($page, [\'markdown\' => $defaults])) instead.', E_USER_DEPRECATED); + } + + parent::__construct(); + + $this->init($excerpts, $defaults); + } +} diff --git a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php new file mode 100644 index 0000000..7804164 --- /dev/null +++ b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php @@ -0,0 +1,319 @@ + $defaults]; + } + $this->excerpts = new Excerpts($excerpts, $defaults); + user_error(__CLASS__ . '::' . __FUNCTION__ . '($page, $defaults) is deprecated since Grav 1.6.10, use ->init(new ' . Excerpts::class . '($page, [\'markdown\' => $defaults])) instead.', E_USER_DEPRECATED); + } else { + $this->excerpts = $excerpts; + } + + $this->BlockTypes['{'][] = 'TwigTag'; + $this->special_chars = ['>' => 'gt', '<' => 'lt', '"' => 'quot']; + + $defaults = $this->excerpts->getConfig(); + + if (isset($defaults['markdown']['auto_line_breaks'])) { + $this->setBreaksEnabled($defaults['markdown']['auto_line_breaks']); + } + if (isset($defaults['markdown']['auto_url_links'])) { + $this->setUrlsLinked($defaults['markdown']['auto_url_links']); + } + if (isset($defaults['markdown']['escape_markup'])) { + $this->setMarkupEscaped($defaults['markdown']['escape_markup']); + } + if (isset($defaults['markdown']['special_chars'])) { + $this->setSpecialChars($defaults['markdown']['special_chars']); + } + + $this->excerpts->fireInitializedEvent($this); + } + + /** + * @return Excerpts + */ + public function getExcerpts() + { + return $this->excerpts; + } + + /** + * Be able to define a new Block type or override an existing one + * + * @param string $type + * @param string $tag + * @param bool $continuable + * @param bool $completable + * @param int|null $index + * @return void + */ + public function addBlockType($type, $tag, $continuable = false, $completable = false, $index = null) + { + $block = &$this->unmarkedBlockTypes; + if ($type) { + if (!isset($this->BlockTypes[$type])) { + $this->BlockTypes[$type] = []; + } + $block = &$this->BlockTypes[$type]; + } + + if (null === $index) { + $block[] = $tag; + } else { + array_splice($block, $index, 0, [$tag]); + } + + if ($continuable) { + $this->continuable_blocks[] = $tag; + } + if ($completable) { + $this->completable_blocks[] = $tag; + } + } + + /** + * Be able to define a new Inline type or override an existing one + * + * @param string $type + * @param string $tag + * @param int|null $index + * @return void + */ + public function addInlineType($type, $tag, $index = null) + { + if (null === $index || !isset($this->InlineTypes[$type])) { + $this->InlineTypes[$type] [] = $tag; + } else { + array_splice($this->InlineTypes[$type], $index, 0, [$tag]); + } + + if (strpos($this->inlineMarkerList, $type) === false) { + $this->inlineMarkerList .= $type; + } + } + + /** + * Overrides the default behavior to allow for plugin-provided blocks to be continuable + * + * @param string $Type + * @return bool + */ + protected function isBlockContinuable($Type) + { + $continuable = in_array($Type, $this->continuable_blocks, true) + || method_exists($this, 'block' . $Type . 'Continue'); + + return $continuable; + } + + /** + * Overrides the default behavior to allow for plugin-provided blocks to be completable + * + * @param string $Type + * @return bool + */ + protected function isBlockCompletable($Type) + { + $completable = in_array($Type, $this->completable_blocks, true) + || method_exists($this, 'block' . $Type . 'Complete'); + + return $completable; + } + + + /** + * Make the element function publicly accessible, Medium uses this to render from Twig + * + * @param array $Element + * @return string markup + */ + public function elementToHtml(array $Element) + { + return $this->element($Element); + } + + /** + * Setter for special chars + * + * @param array $special_chars + * @return $this + */ + public function setSpecialChars($special_chars) + { + $this->special_chars = $special_chars; + + return $this; + } + + /** + * Ensure Twig tags are treated as block level items with no

tags + * + * @param array $line + * @return array|null + */ + protected function blockTwigTag($line) + { + if (preg_match('/(?:{{|{%|{#)(.*)(?:}}|%}|#})/', $line['body'], $matches)) { + return ['markup' => $line['body']]; + } + + return null; + } + + /** + * @param array $excerpt + * @return array|null + */ + protected function inlineSpecialCharacter($excerpt) + { + if ($excerpt['text'][0] === '&' && !preg_match('/^&#?\w+;/', $excerpt['text'])) { + return [ + 'markup' => '&', + 'extent' => 1, + ]; + } + + if (isset($this->special_chars[$excerpt['text'][0]])) { + return [ + 'markup' => '&' . $this->special_chars[$excerpt['text'][0]] . ';', + 'extent' => 1, + ]; + } + + return null; + } + + /** + * @param array $excerpt + * @return array + */ + protected function inlineImage($excerpt) + { + if (preg_match($this->twig_link_regex, $excerpt['text'], $matches)) { + $excerpt['text'] = str_replace($matches[1], '/', $excerpt['text']); + $excerpt = parent::inlineImage($excerpt); + $excerpt['element']['attributes']['src'] = $matches[1]; + $excerpt['extent'] = $excerpt['extent'] + strlen($matches[1]) - 1; + + return $excerpt; + } + + $excerpt['type'] = 'image'; + $excerpt = parent::inlineImage($excerpt); + + // if this is an image process it + if (isset($excerpt['element']['attributes']['src'])) { + $excerpt = $this->excerpts->processImageExcerpt($excerpt); + } + + return $excerpt; + } + + /** + * @param array $excerpt + * @return array + */ + protected function inlineLink($excerpt) + { + $type = $excerpt['type'] ?? 'link'; + + // do some trickery to get around Parsedown requirement for valid URL if its Twig in there + if (preg_match($this->twig_link_regex, $excerpt['text'], $matches)) { + $excerpt['text'] = str_replace($matches[1], '/', $excerpt['text']); + $excerpt = parent::inlineLink($excerpt); + $excerpt['element']['attributes']['href'] = $matches[1]; + $excerpt['extent'] = $excerpt['extent'] + strlen($matches[1]) - 1; + + return $excerpt; + } + + $excerpt = parent::inlineLink($excerpt); + + // if this is a link + if (isset($excerpt['element']['attributes']['href'])) { + $excerpt = $this->excerpts->processLinkExcerpt($excerpt, $type); + } + + return $excerpt; + } + + /** + * For extending this class via plugins + * + * @param string $method + * @param array $args + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function __call($method, $args) + { + + if (isset($this->plugins[$method]) === true) { + $func = $this->plugins[$method]; + + return call_user_func_array($func, $args); + } elseif (isset($this->{$method}) === true) { + $func = $this->{$method}; + + return call_user_func_array($func, $args); + } + + return null; + } + + public function __set($name, $value) + { + if (is_callable($value)) { + $this->plugins[$name] = $value; + } + + } + + +} diff --git a/system/src/Grav/Common/Media/Interfaces/AudioMediaInterface.php b/system/src/Grav/Common/Media/Interfaces/AudioMediaInterface.php new file mode 100644 index 0000000..c666449 --- /dev/null +++ b/system/src/Grav/Common/Media/Interfaces/AudioMediaInterface.php @@ -0,0 +1,25 @@ +set('this.is.my.nested.variable', $value); + * + * @param string $name Dot separated path to the requested value. + * @param mixed $value New value. + * @param string|null $separator Separator, defaults to '.' + * @return $this + */ + public function set($name, $value, $separator = null); +} diff --git a/system/src/Grav/Common/Media/Interfaces/MediaPlayerInterface.php b/system/src/Grav/Common/Media/Interfaces/MediaPlayerInterface.php new file mode 100644 index 0000000..42ba950 --- /dev/null +++ b/system/src/Grav/Common/Media/Interfaces/MediaPlayerInterface.php @@ -0,0 +1,56 @@ + 'user://pages/media']; // Settings from the form field. + * $filename = $media->checkUploadedFile($uploadedFile, $filename, $settings); + * $media->copyUploadedFile($uploadedFile, $filename); + + * @param UploadedFileInterface $uploadedFile + * @param string|null $filename + * @param array|null $settings + * @return string + * @throws RuntimeException + */ + public function checkUploadedFile(UploadedFileInterface $uploadedFile, string $filename = null, array $settings = null): string; + + /** + * Copy uploaded file to the media collection. + * + * WARNING: Always check uploaded file before copying it! + * + * @example + * $filename = null; // Override filename if needed (ignored if randomizing filenames). + * $settings = ['destination' => 'user://pages/media']; // Settings from the form field. + * $filename = $media->checkUploadedFile($uploadedFile, $filename, $settings); + * $media->copyUploadedFile($uploadedFile, $filename); + * + * @param UploadedFileInterface $uploadedFile + * @param string $filename + * @param array|null $settings + * @return void + * @throws RuntimeException + */ + public function copyUploadedFile(UploadedFileInterface $uploadedFile, string $filename, array $settings = null): void; + + /** + * Delete real file from the media collection. + * + * @param string $filename + * @param array|null $settings + * @return void + */ + public function deleteFile(string $filename, array $settings = null): void; + + /** + * Rename file inside the media collection. + * + * @param string $from + * @param string $to + * @param array|null $settings + */ + public function renameFile(string $from, string $to, array $settings = null): void; +} diff --git a/system/src/Grav/Common/Media/Interfaces/VideoMediaInterface.php b/system/src/Grav/Common/Media/Interfaces/VideoMediaInterface.php new file mode 100644 index 0000000..b82206c --- /dev/null +++ b/system/src/Grav/Common/Media/Interfaces/VideoMediaInterface.php @@ -0,0 +1,32 @@ +attributes['controlsList'] = $controlsList; + + return $this; + } + + /** + * Parsedown element for source display mode + * + * @param array $attributes + * @param bool $reset + * @return array + */ + protected function sourceParsedownElement(array $attributes, $reset = true) + { + $location = $this->url($reset); + + return [ + 'name' => 'audio', + 'rawHtml' => 'Your browser does not support the audio tag.', + 'attributes' => $attributes + ]; + } +} diff --git a/system/src/Grav/Common/Media/Traits/ImageDecodingTrait.php b/system/src/Grav/Common/Media/Traits/ImageDecodingTrait.php new file mode 100644 index 0000000..7ea01e9 --- /dev/null +++ b/system/src/Grav/Common/Media/Traits/ImageDecodingTrait.php @@ -0,0 +1,40 @@ +get('system.images.defaults.decoding', 'auto'); + } + + // Validate the provided value (similar to loading) + if ($value !== null && $value !== 'auto') { + $this->attributes['decoding'] = $value; + } + + return $this; + } + +} \ No newline at end of file diff --git a/system/src/Grav/Common/Media/Traits/ImageFetchPriorityTrait.php b/system/src/Grav/Common/Media/Traits/ImageFetchPriorityTrait.php new file mode 100644 index 0000000..af20a97 --- /dev/null +++ b/system/src/Grav/Common/Media/Traits/ImageFetchPriorityTrait.php @@ -0,0 +1,40 @@ +get('system.images.defaults.fetchpriority', 'auto'); + } + + // Validate the provided value (similar to loading and decoding attributes) + if ($value !== null && $value !== 'auto') { + $this->attributes['fetchpriority'] = $value; + } + + return $this; + } + +} \ No newline at end of file diff --git a/system/src/Grav/Common/Media/Traits/ImageLoadingTrait.php b/system/src/Grav/Common/Media/Traits/ImageLoadingTrait.php new file mode 100644 index 0000000..423aba8 --- /dev/null +++ b/system/src/Grav/Common/Media/Traits/ImageLoadingTrait.php @@ -0,0 +1,37 @@ +get('system.images.defaults.loading', 'auto'); + } + if ($value && $value !== 'auto') { + $this->attributes['loading'] = $value; + } + + return $this; + } +} diff --git a/system/src/Grav/Common/Media/Traits/ImageMediaTrait.php b/system/src/Grav/Common/Media/Traits/ImageMediaTrait.php new file mode 100644 index 0000000..59da4cc --- /dev/null +++ b/system/src/Grav/Common/Media/Traits/ImageMediaTrait.php @@ -0,0 +1,428 @@ + [0, 1], + 'forceResize' => [0, 1], + 'cropResize' => [0, 1], + 'crop' => [0, 1, 2, 3], + 'zoomCrop' => [0, 1] + ]; + + /** @var string */ + protected $sizes = '100vw'; + + + /** + * Allows the ability to override the image's pretty name stored in cache + * + * @param string $name + */ + public function setImagePrettyName($name) + { + $this->set('prettyname', $name); + if ($this->image) { + $this->image->setPrettyName($name); + } + } + + /** + * @return string + */ + public function getImagePrettyName() + { + if ($this->get('prettyname')) { + return $this->get('prettyname'); + } + + $basename = $this->get('basename'); + if (preg_match('/[a-z0-9]{40}-(.*)/', $basename, $matches)) { + $basename = $matches[1]; + } + return $basename; + } + + /** + * Simply processes with no extra methods. Useful for triggering events. + * + * @return $this + */ + public function cache() + { + if (!$this->image) { + $this->image(); + } + + return $this; + } + + /** + * Generate alternative image widths, using either an array of integers, or + * a min width, a max width, and a step parameter to fill out the necessary + * widths. Existing image alternatives won't be overwritten. + * + * @param int|int[] $min_width + * @param int $max_width + * @param int $step + * @return $this + */ + public function derivatives($min_width, $max_width = 2500, $step = 200) + { + if (!empty($this->alternatives)) { + $max = max(array_keys($this->alternatives)); + $base = $this->alternatives[$max]; + } else { + $base = $this; + } + + $widths = []; + + if (func_num_args() === 1) { + foreach ((array) func_get_arg(0) as $width) { + if ($width < $base->get('width')) { + $widths[] = $width; + } + } + } else { + $max_width = min($max_width, $base->get('width')); + + for ($width = $min_width; $width < $max_width; $width += $step) { + $widths[] = $width; + } + } + + foreach ($widths as $width) { + // Only generate image alternatives that don't already exist + if (array_key_exists((int) $width, $this->alternatives)) { + continue; + } + + $derivative = MediumFactory::fromFile($base->get('filepath')); + + // It's possible that MediumFactory::fromFile returns null if the + // original image file no longer exists and this class instance was + // retrieved from the page cache + if (null !== $derivative) { + $index = 2; + $alt_widths = array_keys($this->alternatives); + sort($alt_widths); + + foreach ($alt_widths as $i => $key) { + if ($width > $key) { + $index += max($i, 1); + } + } + + $basename = preg_replace('/(@\d+x)?$/', "@{$width}w", $base->get('basename'), 1); + $derivative->setImagePrettyName($basename); + + $ratio = $base->get('width') / $width; + $height = $derivative->get('height') / $ratio; + + $derivative->resize($width, $height); + $derivative->set('width', $width); + $derivative->set('height', $height); + + $this->addAlternative($ratio, $derivative); + } + } + + return $this; + } + + /** + * Clear out the alternatives. + */ + public function clearAlternatives() + { + $this->alternatives = []; + } + + /** + * Sets or gets the quality of the image + * + * @param int|null $quality 0-100 quality + * @return int|$this + */ + public function quality($quality = null) + { + if ($quality) { + if (!$this->image) { + $this->image(); + } + + $this->quality = $quality; + + return $this; + } + + return $this->quality; + } + + /** + * Sets image output format. + * + * @param string $format + * @return $this + */ + public function format($format) + { + if (!$this->image) { + $this->image(); + } + + $this->format = $format; + + return $this; + } + + /** + * Set or get sizes parameter for srcset media action + * + * @param string|null $sizes + * @return string + */ + public function sizes($sizes = null) + { + if ($sizes) { + $this->sizes = $sizes; + + return $this; + } + + return empty($this->sizes) ? '100vw' : $this->sizes; + } + + /** + * Allows to set the width attribute from Markdown or Twig + * Examples: ![Example](myimg.png?width=200&height=400) + * ![Example](myimg.png?resize=100,200&width=100&height=200) + * ![Example](myimg.png?width=auto&height=auto) + * ![Example](myimg.png?width&height) + * {{ page.media['myimg.png'].width().height().html }} + * {{ page.media['myimg.png'].resize(100,200).width(100).height(200).html }} + * + * @param string|int $value A value or 'auto' or empty to use the width of the image + * @return $this + */ + public function width($value = 'auto') + { + if (!$value || $value === 'auto') { + $this->attributes['width'] = $this->get('width'); + } else { + $this->attributes['width'] = $value; + } + + return $this; + } + + /** + * Allows to set the height attribute from Markdown or Twig + * Examples: ![Example](myimg.png?width=200&height=400) + * ![Example](myimg.png?resize=100,200&width=100&height=200) + * ![Example](myimg.png?width=auto&height=auto) + * ![Example](myimg.png?width&height) + * {{ page.media['myimg.png'].width().height().html }} + * {{ page.media['myimg.png'].resize(100,200).width(100).height(200).html }} + * + * @param string|int $value A value or 'auto' or empty to use the height of the image + * @return $this + */ + public function height($value = 'auto') + { + if (!$value || $value === 'auto') { + $this->attributes['height'] = $this->get('height'); + } else { + $this->attributes['height'] = $value; + } + + return $this; + } + + /** + * Filter image by using user defined filter parameters. + * + * @param string $filter Filter to be used. + * @return $this + */ + public function filter($filter = 'image.filters.default') + { + $filters = (array) $this->get($filter, []); + foreach ($filters as $params) { + $params = (array) $params; + $method = array_shift($params); + $this->__call($method, $params); + } + + return $this; + } + + /** + * Return the image higher quality version + * + * @return ImageMediaInterface|$this the alternative version with higher quality + */ + public function higherQualityAlternative() + { + if ($this->alternatives) { + /** @var ImageMedium $max */ + $max = reset($this->alternatives); + /** @var ImageMedium $alternative */ + foreach ($this->alternatives as $alternative) { + if ($alternative->quality() > $max->quality()) { + $max = $alternative; + } + } + + return $max; + } + + return $this; + } + + /** + * Gets medium image, resets image manipulation operations. + * + * @return $this + */ + protected function image() + { + $locator = Grav::instance()['locator']; + + // Use existing cache folder or if it doesn't exist, create it. + $cacheDir = $locator->findResource('cache://images', true) ?: $locator->findResource('cache://images', true, true); + + // Make sure we free previous image. + unset($this->image); + + /** @var MediaCollectionInterface $media */ + $media = $this->get('media'); + if ($media && method_exists($media, 'getImageFileObject')) { + $this->image = $media->getImageFileObject($this); + } else { + $this->image = ImageFile::open($this->get('filepath')); + } + + $this->image + ->setCacheDir($cacheDir) + ->setActualCacheDir($cacheDir) + ->setPrettyName($this->getImagePrettyName()); + + // Fix orientation if enabled + $config = Grav::instance()['config']; + if ($config->get('system.images.auto_fix_orientation', false) && + extension_loaded('exif') && function_exists('exif_read_data')) { + $this->image->fixOrientation(); + } + + // Set CLS configuration + $this->auto_sizes = $config->get('system.images.cls.auto_sizes', false); + $this->aspect_ratio = $config->get('system.images.cls.aspect_ratio', false); + $this->retina_scale = $config->get('system.images.cls.retina_scale', 1); + + $this->watermark = $config->get('system.images.watermark.watermark_all', false); + + return $this; + } + + /** + * Save the image with cache. + * + * @return string + */ + protected function saveImage() + { + if (!$this->image) { + return parent::path(false); + } + + $this->filter(); + + if (isset($this->result)) { + return $this->result; + } + + if ($this->format === 'guess') { + $extension = strtolower($this->get('extension')); + $this->format($extension); + } + + if (!$this->debug_watermarked && $this->get('debug')) { + $ratio = $this->get('ratio'); + if (!$ratio) { + $ratio = 1; + } + + $locator = Grav::instance()['locator']; + $overlay = $locator->findResource("system://assets/responsive-overlays/{$ratio}x.png") ?: $locator->findResource('system://assets/responsive-overlays/unknown.png'); + $this->image->merge(ImageFile::open($overlay)); + } + + if ($this->watermark) { + $this->watermark(); + } + + return $this->image->cacheFile($this->format, $this->quality, false, [$this->get('width'), $this->get('height'), $this->get('modified')]); + } +} diff --git a/system/src/Grav/Common/Media/Traits/MediaFileTrait.php b/system/src/Grav/Common/Media/Traits/MediaFileTrait.php new file mode 100644 index 0000000..9994538 --- /dev/null +++ b/system/src/Grav/Common/Media/Traits/MediaFileTrait.php @@ -0,0 +1,139 @@ +path(false); + + return file_exists($path); + } + + /** + * Get file modification time for the medium. + * + * @return int|null + */ + public function modified() + { + $path = $this->path(false); + if (!file_exists($path)) { + return null; + } + + return filemtime($path) ?: null; + } + + /** + * Get size of the medium. + * + * @return int + */ + public function size() + { + $path = $this->path(false); + if (!file_exists($path)) { + return 0; + } + + return filesize($path) ?: 0; + } + + /** + * Return PATH to file. + * + * @param bool $reset + * @return string path to file + */ + public function path($reset = true) + { + if ($reset) { + $this->reset(); + } + + return $this->get('url') ?? $this->get('filepath'); + } + + /** + * Return the relative path to file + * + * @param bool $reset + * @return string + */ + public function relativePath($reset = true) + { + if ($reset) { + $this->reset(); + } + + $path = $this->path(false); + $output = preg_replace('|^' . preg_quote(GRAV_ROOT, '|') . '|', '', $path) ?: $path; + + /** @var UniformResourceLocator $locator */ + $locator = $this->getGrav()['locator']; + if ($locator->isStream($output)) { + $output = (string)($locator->findResource($output, false) ?: $locator->findResource($output, false, true)); + } + + return $output; + } + + /** + * Return URL to file. + * + * @param bool $reset + * @return string + */ + public function url($reset = true) + { + $url = $this->get('url'); + if ($url) { + return $url; + } + + $path = $this->relativePath($reset); + + return trim($this->getGrav()['base_url'] . '/' . $this->urlQuerystring($path), '\\'); + } + + /** + * Get the URL with full querystring + * + * @param string $url + * @return string + */ + abstract public function urlQuerystring($url); + + /** + * Reset medium. + * + * @return $this + */ + abstract public function reset(); + + /** + * @return Grav + */ + abstract protected function getGrav(): Grav; +} diff --git a/system/src/Grav/Common/Media/Traits/MediaObjectTrait.php b/system/src/Grav/Common/Media/Traits/MediaObjectTrait.php new file mode 100644 index 0000000..eb45618 --- /dev/null +++ b/system/src/Grav/Common/Media/Traits/MediaObjectTrait.php @@ -0,0 +1,630 @@ +getItems()); + } + + /** + * Set querystring to file modification timestamp (or value provided as a parameter). + * + * @param string|int|null $timestamp + * @return $this + */ + public function setTimestamp($timestamp = null) + { + if (null !== $timestamp) { + $this->timestamp = (string)($timestamp); + } elseif ($this instanceof MediaFileInterface) { + $this->timestamp = (string)$this->modified(); + } else { + $this->timestamp = ''; + } + + return $this; + } + + /** + * Returns an array containing just the metadata + * + * @return array + */ + public function metadata() + { + return $this->metadata; + } + + /** + * Add meta file for the medium. + * + * @param string $filepath + */ + abstract public function addMetaFile($filepath); + + /** + * Add alternative Medium to this Medium. + * + * @param int|float $ratio + * @param MediaObjectInterface $alternative + */ + public function addAlternative($ratio, MediaObjectInterface $alternative) + { + if (!is_numeric($ratio) || $ratio === 0) { + return; + } + + $alternative->set('ratio', $ratio); + $width = $alternative->get('width', 0); + + $this->alternatives[$width] = $alternative; + } + + /** + * @param bool $withDerived + * @return array + */ + public function getAlternatives(bool $withDerived = true): array + { + $alternatives = []; + foreach ($this->alternatives + [$this->get('width', 0) => $this] as $size => $alternative) { + if ($withDerived || $alternative->filename === Utils::basename($alternative->filepath)) { + $alternatives[$size] = $alternative; + } + } + + ksort($alternatives, SORT_NUMERIC); + + return $alternatives; + } + + /** + * Return string representation of the object (html). + * + * @return string + */ + #[\ReturnTypeWillChange] + abstract public function __toString(); + + /** + * Get/set querystring for the file's url + * + * @param string|null $querystring + * @param bool $withQuestionmark + * @return string + */ + public function querystring($querystring = null, $withQuestionmark = true) + { + if (null !== $querystring) { + $this->medium_querystring[] = ltrim($querystring, '?&'); + foreach ($this->alternatives as $alt) { + $alt->querystring($querystring, $withQuestionmark); + } + } + + if (empty($this->medium_querystring)) { + return ''; + } + + // join the strings + $querystring = implode('&', $this->medium_querystring); + // explode all strings + $query_parts = explode('&', $querystring); + // Join them again now ensure the elements are unique + $querystring = implode('&', array_unique($query_parts)); + + return $withQuestionmark ? ('?' . $querystring) : $querystring; + } + + /** + * Get the URL with full querystring + * + * @param string $url + * @return string + */ + public function urlQuerystring($url) + { + $querystring = $this->querystring(); + if (isset($this->timestamp) && !Utils::contains($querystring, $this->timestamp)) { + $querystring = empty($querystring) ? ('?' . $this->timestamp) : ($querystring . '&' . $this->timestamp); + } + + return ltrim($url . $querystring . $this->urlHash(), '/'); + } + + /** + * Get/set hash for the file's url + * + * @param string|null $hash + * @param bool $withHash + * @return string + */ + public function urlHash($hash = null, $withHash = true) + { + if ($hash) { + $this->set('urlHash', ltrim($hash, '#')); + } + + $hash = $this->get('urlHash', ''); + + return $withHash && !empty($hash) ? '#' . $hash : $hash; + } + + /** + * Get an element (is array) that can be rendered by the Parsedown engine + * + * @param string|null $title + * @param string|null $alt + * @param string|null $class + * @param string|null $id + * @param bool $reset + * @return array + */ + public function parsedownElement($title = null, $alt = null, $class = null, $id = null, $reset = true) + { + $attributes = $this->attributes; + $items = $this->getItems(); + + $style = ''; + foreach ($this->styleAttributes as $key => $value) { + if (is_numeric($key)) { // Special case for inline style attributes, refer to style() method + $style .= $value; + } else { + $style .= $key . ': ' . $value . ';'; + } + } + if ($style) { + $attributes['style'] = $style; + } + + if (empty($attributes['title'])) { + if (!empty($title)) { + $attributes['title'] = $title; + } elseif (!empty($items['title'])) { + $attributes['title'] = $items['title']; + } + } + + if (empty($attributes['alt'])) { + if (!empty($alt)) { + $attributes['alt'] = $alt; + } elseif (!empty($items['alt'])) { + $attributes['alt'] = $items['alt']; + } elseif (!empty($items['alt_text'])) { + $attributes['alt'] = $items['alt_text']; + } else { + $attributes['alt'] = ''; + } + } + + if (empty($attributes['class'])) { + if (!empty($class)) { + $attributes['class'] = $class; + } elseif (!empty($items['class'])) { + $attributes['class'] = $items['class']; + } + } + + if (empty($attributes['id'])) { + if (!empty($id)) { + $attributes['id'] = $id; + } elseif (!empty($items['id'])) { + $attributes['id'] = $items['id']; + } + } + + switch ($this->mode) { + case 'text': + $element = $this->textParsedownElement($attributes, false); + break; + case 'thumbnail': + $thumbnail = $this->getThumbnail(); + $element = $thumbnail ? $thumbnail->sourceParsedownElement($attributes, false) : []; + break; + case 'source': + $element = $this->sourceParsedownElement($attributes, false); + break; + default: + $element = []; + } + + if ($reset) { + $this->reset(); + } + + $this->display('source'); + + return $element; + } + + /** + * Reset medium. + * + * @return $this + */ + public function reset() + { + $this->attributes = []; + + return $this; + } + + /** + * Add custom attribute to medium. + * + * @param string $attribute + * @param string $value + * @return $this + */ + public function attribute($attribute = null, $value = '') + { + if (!empty($attribute)) { + $this->attributes[$attribute] = $value; + } + return $this; + } + + /** + * Switch display mode. + * + * @param string $mode + * + * @return MediaObjectInterface|null + */ + public function display($mode = 'source') + { + if ($this->mode === $mode) { + return $this; + } + + $this->mode = $mode; + if ($mode === 'thumbnail') { + $thumbnail = $this->getThumbnail(); + + return $thumbnail ? $thumbnail->reset() : null; + } + + return $this->reset(); + } + + /** + * Helper method to determine if this media item has a thumbnail or not + * + * @param string $type; + * @return bool + */ + public function thumbnailExists($type = 'page') + { + $thumbs = $this->get('thumbnails'); + + return isset($thumbs[$type]); + } + + /** + * Switch thumbnail. + * + * @param string $type + * @return $this + */ + public function thumbnail($type = 'auto') + { + if ($type !== 'auto' && !in_array($type, $this->thumbnailTypes, true)) { + return $this; + } + + if ($this->thumbnailType !== $type) { + $this->_thumbnail = null; + } + + $this->thumbnailType = $type; + + return $this; + } + + /** + * Return URL to file. + * + * @param bool $reset + * @return string + */ + abstract public function url($reset = true); + + /** + * Turn the current Medium into a Link + * + * @param bool $reset + * @param array $attributes + * @return MediaLinkInterface + */ + public function link($reset = true, array $attributes = []) + { + if ($this->mode !== 'source') { + $this->display('source'); + } + + foreach ($this->attributes as $key => $value) { + empty($attributes['data-' . $key]) && $attributes['data-' . $key] = $value; + } + + empty($attributes['href']) && $attributes['href'] = $this->url(); + + return $this->createLink($attributes); + } + + /** + * Turn the current Medium into a Link with lightbox enabled + * + * @param int|null $width + * @param int|null $height + * @param bool $reset + * @return MediaLinkInterface + */ + public function lightbox($width = null, $height = null, $reset = true) + { + $attributes = ['rel' => 'lightbox']; + + if ($width && $height) { + $attributes['data-width'] = $width; + $attributes['data-height'] = $height; + } + + return $this->link($reset, $attributes); + } + + /** + * Add a class to the element from Markdown or Twig + * Example: ![Example](myimg.png?classes=float-left) or ![Example](myimg.png?classes=myclass1,myclass2) + * + * @return $this + */ + public function classes() + { + $classes = func_get_args(); + if (!empty($classes)) { + $this->attributes['class'] = implode(',', $classes); + } + + return $this; + } + + /** + * Add an id to the element from Markdown or Twig + * Example: ![Example](myimg.png?id=primary-img) + * + * @param string $id + * @return $this + */ + public function id($id) + { + if (is_string($id)) { + $this->attributes['id'] = trim($id); + } + + return $this; + } + + /** + * Allows to add an inline style attribute from Markdown or Twig + * Example: ![Example](myimg.png?style=float:left) + * + * @param string $style + * @return $this + */ + public function style($style) + { + $this->styleAttributes[] = rtrim($style, ';') . ';'; + + return $this; + } + + /** + * Allow any action to be called on this medium from twig or markdown + * + * @param string $method + * @param array $args + * @return $this + */ + #[\ReturnTypeWillChange] + public function __call($method, $args) + { + $count = count($args); + if ($count > 1 || ($count === 1 && !empty($args[0]))) { + $method .= '=' . implode(',', array_map(static function ($a) { + if (is_array($a)) { + $a = '[' . implode(',', $a) . ']'; + } + + return rawurlencode($a); + }, $args)); + } + + if (!empty($method)) { + $this->querystring($this->querystring(null, false) . '&' . $method); + } + + return $this; + } + + /** + * Parsedown element for source display mode + * + * @param array $attributes + * @param bool $reset + * @return array + */ + protected function sourceParsedownElement(array $attributes, $reset = true) + { + return $this->textParsedownElement($attributes, $reset); + } + + /** + * Parsedown element for text display mode + * + * @param array $attributes + * @param bool $reset + * @return array + */ + protected function textParsedownElement(array $attributes, $reset = true) + { + if ($reset) { + $this->reset(); + } + + $text = $attributes['title'] ?? ''; + if ($text === '') { + $text = $attributes['alt'] ?? ''; + if ($text === '') { + $text = $this->get('filename'); + } + } + + return [ + 'name' => 'p', + 'attributes' => $attributes, + 'text' => $text + ]; + } + + /** + * Get the thumbnail Medium object + * + * @return ThumbnailImageMedium|null + */ + protected function getThumbnail() + { + if (null === $this->_thumbnail) { + $types = $this->thumbnailTypes; + + if ($this->thumbnailType !== 'auto') { + array_unshift($types, $this->thumbnailType); + } + + foreach ($types as $type) { + $thumb = $this->get("thumbnails.{$type}", false); + if ($thumb) { + $image = $thumb instanceof ThumbnailImageMedium ? $thumb : $this->createThumbnail($thumb); + if($image) { + $image->parent = $this; + $this->_thumbnail = $image; + } + break; + } + } + } + + return $this->_thumbnail; + } + + /** + * Get value by using dot notation for nested arrays/objects. + * + * @example $value = $this->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|null $separator Separator, defaults to '.' + * @return mixed Value. + */ + abstract public function get($name, $default = null, $separator = null); + + /** + * Set value by using dot notation for nested arrays/objects. + * + * @example $data->set('this.is.my.nested.variable', $value); + * + * @param string $name Dot separated path to the requested value. + * @param mixed $value New value. + * @param string|null $separator Separator, defaults to '.' + * @return $this + */ + abstract public function set($name, $value, $separator = null); + + /** + * @param string $thumb + */ + abstract protected function createThumbnail($thumb); + + /** + * @param array $attributes + * @return MediaLinkInterface + */ + abstract protected function createLink(array $attributes); + + /** + * @return array + */ + abstract protected function getItems(): array; +} diff --git a/system/src/Grav/Common/Media/Traits/MediaPlayerTrait.php b/system/src/Grav/Common/Media/Traits/MediaPlayerTrait.php new file mode 100644 index 0000000..101c6a6 --- /dev/null +++ b/system/src/Grav/Common/Media/Traits/MediaPlayerTrait.php @@ -0,0 +1,113 @@ +attributes['controls'] = 'controls'; + } else { + unset($this->attributes['controls']); + } + + return $this; + } + + /** + * Allows to set the loop attribute + * + * @param bool $status + * @return $this + */ + public function loop($status = false) + { + if ($status) { + $this->attributes['loop'] = 'loop'; + } else { + unset($this->attributes['loop']); + } + + return $this; + } + + /** + * Allows to set the autoplay attribute + * + * @param bool $status + * @return $this + */ + public function autoplay($status = false) + { + if ($status) { + $this->attributes['autoplay'] = 'autoplay'; + } else { + unset($this->attributes['autoplay']); + } + + return $this; + } + + /** + * Allows to set the muted attribute + * + * @param bool $status + * @return $this + */ + public function muted($status = false) + { + if ($status) { + $this->attributes['muted'] = 'muted'; + } else { + unset($this->attributes['muted']); + } + + return $this; + } + + /** + * Allows to set the preload behaviour + * + * @param string|null $preload + * @return $this + */ + public function preload($preload = null) + { + $validPreloadAttrs = ['auto', 'metadata', 'none']; + + if (null === $preload) { + unset($this->attributes['preload']); + } elseif (in_array($preload, $validPreloadAttrs, true)) { + $this->attributes['preload'] = $preload; + } + + return $this; + } + + /** + * Reset player. + */ + public function resetPlayer() + { + $this->attributes['controls'] = 'controls'; + } +} diff --git a/system/src/Grav/Common/Media/Traits/MediaTrait.php b/system/src/Grav/Common/Media/Traits/MediaTrait.php new file mode 100644 index 0000000..09caeaa --- /dev/null +++ b/system/src/Grav/Common/Media/Traits/MediaTrait.php @@ -0,0 +1,153 @@ +getMediaFolder(); + if (!$folder) { + return null; + } + + if (strpos($folder, '://')) { + return $folder; + } + + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + $user = $locator->findResource('user://'); + if (strpos($folder, $user) === 0) { + return 'user://' . substr($folder, strlen($user)+1); + } + + return null; + } + + /** + * Gets the associated media collection. + * + * @return MediaCollectionInterface|Media Representation of associated media. + */ + public function getMedia() + { + $media = $this->media; + if (null === $media) { + $cache = $this->getMediaCache(); + $cacheKey = md5('media' . $this->getCacheKey()); + + // Use cached media if possible. + $media = $cache->get($cacheKey); + if (!$media instanceof MediaCollectionInterface) { + $media = new Media($this->getMediaFolder(), $this->getMediaOrder(), $this->_loadMedia); + $cache->set($cacheKey, $media); + } + + $this->media = $media; + } + + return $media; + } + + /** + * Sets the associated media collection. + * + * @param MediaCollectionInterface|Media $media Representation of associated media. + * @return $this + */ + protected function setMedia(MediaCollectionInterface $media) + { + $cache = $this->getMediaCache(); + $cacheKey = md5('media' . $this->getCacheKey()); + $cache->set($cacheKey, $media); + + $this->media = $media; + + return $this; + } + + /** + * @return void + */ + protected function freeMedia() + { + $this->media = null; + } + + /** + * Clear media cache. + * + * @return void + */ + protected function clearMediaCache() + { + $cache = $this->getMediaCache(); + $cacheKey = md5('media' . $this->getCacheKey()); + $cache->delete($cacheKey); + + $this->freeMedia(); + } + + /** + * @return CacheInterface + */ + protected function getMediaCache() + { + /** @var Cache $cache */ + $cache = Grav::instance()['cache']; + + return $cache->getSimpleCache(); + } + + /** + * @return string + */ + abstract protected function getCacheKey(): string; +} diff --git a/system/src/Grav/Common/Media/Traits/MediaUploadTrait.php b/system/src/Grav/Common/Media/Traits/MediaUploadTrait.php new file mode 100644 index 0000000..75c00e0 --- /dev/null +++ b/system/src/Grav/Common/Media/Traits/MediaUploadTrait.php @@ -0,0 +1,680 @@ + true, // Whether path is in the media collection path itself. + 'avoid_overwriting' => false, // Do not override existing files (adds datetime postfix if conflict). + 'random_name' => false, // True if name needs to be randomized. + 'accept' => ['image/*'], // Accepted mime types or file extensions. + 'limit' => 10, // Maximum number of files. + 'filesize' => null, // Maximum filesize in MB. + 'destination' => null // Destination path, if empty, exception is thrown. + ]; + + /** + * Create Medium from an uploaded file. + * + * @param UploadedFileInterface $uploadedFile + * @param array $params + * @return Medium|null + */ + public function createFromUploadedFile(UploadedFileInterface $uploadedFile, array $params = []) + { + return MediumFactory::fromUploadedFile($uploadedFile, $params); + } + + /** + * Checks that uploaded file meets the requirements. Returns new filename. + * + * @example + * $filename = null; // Override filename if needed (ignored if randomizing filenames). + * $settings = ['destination' => 'user://pages/media']; // Settings from the form field. + * $filename = $media->checkUploadedFile($uploadedFile, $filename, $settings); + * $media->copyUploadedFile($uploadedFile, $filename); + * + * @param UploadedFileInterface $uploadedFile + * @param string|null $filename + * @param array|null $settings + * @return string + * @throws RuntimeException + */ + public function checkUploadedFile(UploadedFileInterface $uploadedFile, string $filename = null, array $settings = null): string + { + // Check if there is an upload error. + switch ($uploadedFile->getError()) { + case UPLOAD_ERR_OK: + break; + case UPLOAD_ERR_INI_SIZE: + case UPLOAD_ERR_FORM_SIZE: + throw new RuntimeException($this->translate('PLUGIN_ADMIN.EXCEEDED_FILESIZE_LIMIT'), 400); + case UPLOAD_ERR_PARTIAL: + case UPLOAD_ERR_NO_FILE: + if (!$uploadedFile instanceof FormFlashFile) { + throw new RuntimeException($this->translate('PLUGIN_ADMIN.NO_FILES_SENT'), 400); + } + break; + case UPLOAD_ERR_NO_TMP_DIR: + throw new RuntimeException($this->translate('PLUGIN_ADMIN.UPLOAD_ERR_NO_TMP_DIR'), 400); + case UPLOAD_ERR_CANT_WRITE: + case UPLOAD_ERR_EXTENSION: + default: + throw new RuntimeException($this->translate('PLUGIN_ADMIN.UNKNOWN_ERRORS'), 400); + } + + $metadata = [ + 'filename' => $uploadedFile->getClientFilename(), + 'mime' => $uploadedFile->getClientMediaType(), + 'size' => $uploadedFile->getSize(), + ]; + + if ($uploadedFile instanceof FormFlashFile) { + $uploadedFile->checkXss(); + } + + return $this->checkFileMetadata($metadata, $filename, $settings); + } + + /** + * Checks that file metadata meets the requirements. Returns new filename. + * + * @param array $metadata + * @param array|null $settings + * @return string + * @throws RuntimeException + */ + public function checkFileMetadata(array $metadata, string $filename = null, array $settings = null): string + { + // Add the defaults to the settings. + $settings = $this->getUploadSettings($settings); + + // Destination is always needed (but it can be set in defaults). + $self = $settings['self'] ?? false; + if (!isset($settings['destination']) && $self === false) { + throw new RuntimeException($this->translate('PLUGIN_ADMIN.DESTINATION_NOT_SPECIFIED'), 400); + } + + if (null === $filename) { + // If no filename is given, use the filename from the uploaded file (path is not allowed). + $folder = ''; + $filename = $metadata['filename'] ?? ''; + } else { + // If caller sets the filename, we will accept any custom path. + $folder = dirname($filename); + if ($folder === '.') { + $folder = ''; + } + $filename = Utils::basename($filename); + } + $extension = Utils::pathinfo($filename, PATHINFO_EXTENSION); + + // Decide which filename to use. + if ($settings['random_name']) { + // Generate random filename if asked for. + $filename = mb_strtolower(Utils::generateRandomString(15) . '.' . $extension); + } + + // Handle conflicting filename if needed. + if ($settings['avoid_overwriting']) { + $destination = $settings['destination']; + if ($destination && $this->fileExists($filename, $destination)) { + $filename = date('YmdHis') . '-' . $filename; + } + } + $filepath = $folder . $filename; + + // Check if the filename is allowed. + if (!Utils::checkFilename($filepath)) { + throw new RuntimeException( + sprintf($this->translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_UPLOAD'), $filepath, $this->translate('PLUGIN_ADMIN.BAD_FILENAME')) + ); + } + + // Check if the file extension is allowed. + $extension = mb_strtolower($extension); + if (!$extension || !$this->getConfig()->get("media.types.{$extension}")) { + // Not a supported type. + throw new RuntimeException($this->translate('PLUGIN_ADMIN.UNSUPPORTED_FILE_TYPE') . ': ' . $extension, 400); + } + + // Calculate maximum file size (from MB). + $filesize = $settings['filesize']; + if ($filesize) { + $max_filesize = $filesize * 1048576; + if ($metadata['size'] > $max_filesize) { + // TODO: use own language string + throw new RuntimeException($this->translate('PLUGIN_ADMIN.EXCEEDED_GRAV_FILESIZE_LIMIT'), 400); + } + } elseif (null === $filesize) { + // Check size against the Grav upload limit. + $grav_limit = Utils::getUploadLimit(); + if ($grav_limit > 0 && $metadata['size'] > $grav_limit) { + throw new RuntimeException($this->translate('PLUGIN_ADMIN.EXCEEDED_GRAV_FILESIZE_LIMIT'), 400); + } + } + + $grav = Grav::instance(); + /** @var MimeTypes $mimeChecker */ + $mimeChecker = $grav['mime']; + + // Handle Accepted file types. Accept can only be mime types (image/png | image/*) or file extensions (.pdf | .jpg) + // Do not trust mime type sent by the browser. + $mime = $metadata['mime'] ?? $mimeChecker->getMimeType($extension); + $validExtensions = $mimeChecker->getExtensions($mime); + if (!in_array($extension, $validExtensions, true)) { + throw new RuntimeException('The mime type does not match to file extension', 400); + } + + $accepted = false; + $errors = []; + foreach ((array)$settings['accept'] as $type) { + // Force acceptance of any file when star notation + if ($type === '*') { + $accepted = true; + break; + } + + $isMime = strstr($type, '/'); + $find = str_replace(['.', '*', '+'], ['\.', '.*', '\+'], $type); + + if ($isMime) { + $match = preg_match('#' . $find . '$#', $mime); + if (!$match) { + // TODO: translate + $errors[] = 'The MIME type "' . $mime . '" for the file "' . $filepath . '" is not an accepted.'; + } else { + $accepted = true; + break; + } + } else { + $match = preg_match('#' . $find . '$#', $filename); + if (!$match) { + // TODO: translate + $errors[] = 'The File Extension for the file "' . $filepath . '" is not an accepted.'; + } else { + $accepted = true; + break; + } + } + } + if (!$accepted) { + throw new RuntimeException(implode('
', $errors), 400); + } + + return $filepath; + } + + /** + * Copy uploaded file to the media collection. + * + * WARNING: Always check uploaded file before copying it! + * + * @example + * $settings = ['destination' => 'user://pages/media']; // Settings from the form field. + * $filename = $media->checkUploadedFile($uploadedFile, $filename, $settings); + * $media->copyUploadedFile($uploadedFile, $filename, $settings); + * + * @param UploadedFileInterface $uploadedFile + * @param string $filename + * @param array|null $settings + * @return void + * @throws RuntimeException + */ + public function copyUploadedFile(UploadedFileInterface $uploadedFile, string $filename, array $settings = null): void + { + // Add the defaults to the settings. + $settings = $this->getUploadSettings($settings); + + $path = $settings['destination'] ?? $this->getPath(); + if (!$path || !$filename) { + throw new RuntimeException($this->translate('PLUGIN_ADMIN.FAILED_TO_MOVE_UPLOADED_FILE'), 400); + } + + /** @var UniformResourceLocator $locator */ + $locator = $this->getGrav()['locator']; + + try { + // Clear locator cache to make sure we have up to date information from the filesystem. + $locator->clearCache(); + $this->clearCache(); + + $filesystem = Filesystem::getInstance(false); + + // Calculate path without the retina scaling factor. + $basename = $filesystem->basename($filename); + $pathname = $filesystem->pathname($filename); + + // Get name for the uploaded file. + [$base, $ext,,] = $this->getFileParts($basename); + $name = "{$pathname}{$base}.{$ext}"; + + // Upload file. + if ($uploadedFile instanceof FormFlashFile) { + // FormFlashFile needs some additional logic. + if ($uploadedFile->getError() === \UPLOAD_ERR_OK) { + // Move uploaded file. + $this->doMoveUploadedFile($uploadedFile, $filename, $path); + } elseif (strpos($filename, 'original/') === 0 && !$this->fileExists($filename, $path) && $this->fileExists($basename, $path)) { + // Original image support: override original image if it's the same as the uploaded image. + $this->doCopy($basename, $filename, $path); + } + + // FormFlashFile may also contain metadata. + $metadata = $uploadedFile->getMetaData(); + if ($metadata) { + // TODO: This overrides metadata if used with multiple retina image sizes. + $this->doSaveMetadata(['upload' => $metadata], $name, $path); + } + } else { + // Not a FormFlashFile. + $this->doMoveUploadedFile($uploadedFile, $filename, $path); + } + + // Post-processing: Special content sanitization for SVG. + $mime = Utils::getMimeByFilename($filename); + if (Utils::contains($mime, 'svg', false)) { + $this->doSanitizeSvg($filename, $path); + } + + // Add the new file into the media. + // TODO: This overrides existing media sizes if used with multiple retina image sizes. + $this->doAddUploadedMedium($name, $filename, $path); + + } catch (Exception $e) { + throw new RuntimeException($this->translate('PLUGIN_ADMIN.FAILED_TO_MOVE_UPLOADED_FILE') . $e->getMessage(), 400); + } finally { + // Finally clear media cache. + $locator->clearCache(); + $this->clearCache(); + } + } + + /** + * Delete real file from the media collection. + * + * @param string $filename + * @param array|null $settings + * @return void + * @throws RuntimeException + */ + public function deleteFile(string $filename, array $settings = null): void + { + // Add the defaults to the settings. + $settings = $this->getUploadSettings($settings); + $filesystem = Filesystem::getInstance(false); + + // First check for allowed filename. + $basename = $filesystem->basename($filename); + if (!Utils::checkFilename($basename)) { + throw new RuntimeException($this->translate('PLUGIN_ADMIN.FILE_COULD_NOT_BE_DELETED') . ": {$this->translate('PLUGIN_ADMIN.BAD_FILENAME')}: " . $filename, 400); + } + + $path = $settings['destination'] ?? $this->getPath(); + if (!$path) { + return; + } + + /** @var UniformResourceLocator $locator */ + $locator = $this->getGrav()['locator']; + $locator->clearCache(); + + $pathname = $filesystem->pathname($filename); + + // Get base name of the file. + [$base, $ext,,] = $this->getFileParts($basename); + $name = "{$pathname}{$base}.{$ext}"; + + // Remove file and all all the associated metadata. + $this->doRemove($name, $path); + + // Finally clear media cache. + $locator->clearCache(); + $this->clearCache(); + } + + /** + * Rename file inside the media collection. + * + * @param string $from + * @param string $to + * @param array|null $settings + */ + public function renameFile(string $from, string $to, array $settings = null): void + { + // Add the defaults to the settings. + $settings = $this->getUploadSettings($settings); + $filesystem = Filesystem::getInstance(false); + + $path = $settings['destination'] ?? $this->getPath(); + if (!$path) { + // TODO: translate error message + throw new RuntimeException('Failed to rename file: Bad destination', 400); + } + + /** @var UniformResourceLocator $locator */ + $locator = $this->getGrav()['locator']; + $locator->clearCache(); + + // Get base name of the file. + $pathname = $filesystem->pathname($from); + + // Remove @2x, @3x and .meta.yaml + [$base, $ext,,] = $this->getFileParts($filesystem->basename($from)); + $from = "{$pathname}{$base}.{$ext}"; + + [$base, $ext,,] = $this->getFileParts($filesystem->basename($to)); + $to = "{$pathname}{$base}.{$ext}"; + + $this->doRename($from, $to, $path); + + // Finally clear media cache. + $locator->clearCache(); + $this->clearCache(); + } + + /** + * Internal logic to move uploaded file. + * + * @param UploadedFileInterface $uploadedFile + * @param string $filename + * @param string $path + */ + protected function doMoveUploadedFile(UploadedFileInterface $uploadedFile, string $filename, string $path): void + { + $filepath = sprintf('%s/%s', $path, $filename); + + /** @var UniformResourceLocator $locator */ + $locator = $this->getGrav()['locator']; + + // Do not use streams internally. + if ($locator->isStream($filepath)) { + $filepath = (string)$locator->findResource($filepath, true, true); + } + + Folder::create(dirname($filepath)); + + $uploadedFile->moveTo($filepath); + } + + /** + * Get upload settings. + * + * @param array|null $settings Form field specific settings (override). + * @return array + */ + public function getUploadSettings(?array $settings = null): array + { + return null !== $settings ? $settings + $this->_upload_defaults : $this->_upload_defaults; + } + + /** + * Internal logic to copy file. + * + * @param string $src + * @param string $dst + * @param string $path + */ + protected function doCopy(string $src, string $dst, string $path): void + { + $src = sprintf('%s/%s', $path, $src); + $dst = sprintf('%s/%s', $path, $dst); + + /** @var UniformResourceLocator $locator */ + $locator = $this->getGrav()['locator']; + + // Do not use streams internally. + if ($locator->isStream($dst)) { + $dst = (string)$locator->findResource($dst, true, true); + } + + Folder::create(dirname($dst)); + + copy($src, $dst); + } + + /** + * Internal logic to rename file. + * + * @param string $from + * @param string $to + * @param string $path + */ + protected function doRename(string $from, string $to, string $path): void + { + /** @var UniformResourceLocator $locator */ + $locator = $this->getGrav()['locator']; + + $fromPath = $path . '/' . $from; + if ($locator->isStream($fromPath)) { + $fromPath = $locator->findResource($fromPath, true, true); + } + + if (!is_file($fromPath)) { + return; + } + + $mediaPath = dirname($fromPath); + $toPath = $mediaPath . '/' . $to; + if ($locator->isStream($toPath)) { + $toPath = $locator->findResource($toPath, true, true); + } + + if (is_file($toPath)) { + // TODO: translate error message + throw new RuntimeException(sprintf('File could not be renamed: %s already exists (%s)', $to, $mediaPath), 500); + } + + $result = rename($fromPath, $toPath); + if (!$result) { + // TODO: translate error message + throw new RuntimeException(sprintf('File could not be renamed: %s -> %s (%s)', $from, $to, $mediaPath), 500); + } + + // TODO: Add missing logic to handle retina files. + if (is_file($fromPath . '.meta.yaml')) { + $result = rename($fromPath . '.meta.yaml', $toPath . '.meta.yaml'); + if (!$result) { + // TODO: translate error message + throw new RuntimeException(sprintf('Meta could not be renamed: %s -> %s (%s)', $from, $to, $mediaPath), 500); + } + } + } + + /** + * Internal logic to remove file. + * + * @param string $filename + * @param string $path + */ + protected function doRemove(string $filename, string $path): void + { + $filesystem = Filesystem::getInstance(false); + + /** @var UniformResourceLocator $locator */ + $locator = $this->getGrav()['locator']; + + // If path doesn't exist, there's nothing to do. + $pathname = $filesystem->pathname($filename); + if (!$this->fileExists($pathname, $path)) { + return; + } + + $folder = $locator->isStream($path) ? (string)$locator->findResource($path, true, true) : $path; + + // Remove requested media file. + if ($this->fileExists($filename, $path)) { + $result = unlink("{$folder}/{$filename}"); + if (!$result) { + throw new RuntimeException($this->translate('PLUGIN_ADMIN.FILE_COULD_NOT_BE_DELETED') . ': ' . $filename, 500); + } + } + + // Remove associated metadata. + $this->doRemoveMetadata($filename, $path); + + // Remove associated 2x, 3x and their .meta.yaml files. + $targetPath = rtrim(sprintf('%s/%s', $folder, $pathname), '/'); + $dir = scandir($targetPath, SCANDIR_SORT_NONE); + if (false === $dir) { + throw new RuntimeException($this->translate('PLUGIN_ADMIN.FILE_COULD_NOT_BE_DELETED') . ': ' . $filename, 500); + } + + /** @var UniformResourceLocator $locator */ + $locator = $this->getGrav()['locator']; + + $basename = $filesystem->basename($filename); + $fileParts = (array)$filesystem->pathinfo($filename); + + foreach ($dir as $file) { + $preg_name = preg_quote($fileParts['filename'], '`'); + $preg_ext = preg_quote($fileParts['extension'] ?? '.', '`'); + $preg_filename = preg_quote($basename, '`'); + + if (preg_match("`({$preg_name}@\d+x\.{$preg_ext}(?:\.meta\.yaml)?$|{$preg_filename}\.meta\.yaml)$`", $file)) { + $testPath = $targetPath . '/' . $file; + if ($locator->isStream($testPath)) { + $testPath = (string)$locator->findResource($testPath, true, true); + $locator->clearCache($testPath); + } + + if (is_file($testPath)) { + $result = unlink($testPath); + if (!$result) { + throw new RuntimeException($this->translate('PLUGIN_ADMIN.FILE_COULD_NOT_BE_DELETED') . ': ' . $filename, 500); + } + } + } + } + + $this->hide($filename); + } + + /** + * @param array $metadata + * @param string $filename + * @param string $path + */ + protected function doSaveMetadata(array $metadata, string $filename, string $path): void + { + $filepath = sprintf('%s/%s', $path, $filename); + + /** @var UniformResourceLocator $locator */ + $locator = $this->getGrav()['locator']; + + // Do not use streams internally. + if ($locator->isStream($filepath)) { + $filepath = (string)$locator->findResource($filepath, true, true); + } + + $file = YamlFile::instance($filepath . '.meta.yaml'); + $file->save($metadata); + } + + /** + * @param string $filename + * @param string $path + */ + protected function doRemoveMetadata(string $filename, string $path): void + { + $filepath = sprintf('%s/%s', $path, $filename); + + /** @var UniformResourceLocator $locator */ + $locator = $this->getGrav()['locator']; + + // Do not use streams internally. + if ($locator->isStream($filepath)) { + $filepath = (string)$locator->findResource($filepath, true); + if (!$filepath) { + return; + } + } + + $file = YamlFile::instance($filepath . '.meta.yaml'); + if ($file->exists()) { + $file->delete(); + } + } + + /** + * @param string $filename + * @param string $path + */ + protected function doSanitizeSvg(string $filename, string $path): void + { + $filepath = sprintf('%s/%s', $path, $filename); + + /** @var UniformResourceLocator $locator */ + $locator = $this->getGrav()['locator']; + + // Do not use streams internally. + if ($locator->isStream($filepath)) { + $filepath = (string)$locator->findResource($filepath, true, true); + } + + Security::sanitizeSVG($filepath); + } + + /** + * @param string $name + * @param string $filename + * @param string $path + */ + protected function doAddUploadedMedium(string $name, string $filename, string $path): void + { + $filepath = sprintf('%s/%s', $path, $filename); + $medium = $this->createFromFile($filepath); + $realpath = $path . '/' . $name; + $this->add($realpath, $medium); + } + + /** + * @param string $string + * @return string + */ + protected function translate(string $string): string + { + return $this->getLanguage()->translate($string); + } + + abstract protected function getPath(): ?string; + + abstract protected function getGrav(): Grav; + + abstract protected function getConfig(): Config; + + abstract protected function getLanguage(): Language; + + abstract protected function clearCache(): void; +} diff --git a/system/src/Grav/Common/Media/Traits/StaticResizeTrait.php b/system/src/Grav/Common/Media/Traits/StaticResizeTrait.php new file mode 100644 index 0000000..83fb205 --- /dev/null +++ b/system/src/Grav/Common/Media/Traits/StaticResizeTrait.php @@ -0,0 +1,40 @@ +styleAttributes['width'] = $width . 'px'; + } else { + unset($this->styleAttributes['width']); + } + if ($height) { + $this->styleAttributes['height'] = $height . 'px'; + } else { + unset($this->styleAttributes['height']); + } + + return $this; + } +} diff --git a/system/src/Grav/Common/Media/Traits/ThumbnailMediaTrait.php b/system/src/Grav/Common/Media/Traits/ThumbnailMediaTrait.php new file mode 100644 index 0000000..d65d073 --- /dev/null +++ b/system/src/Grav/Common/Media/Traits/ThumbnailMediaTrait.php @@ -0,0 +1,149 @@ +bubble('parsedownElement', [$title, $alt, $class, $id, $reset]); + } + + /** + * Return HTML markup from the medium. + * + * @param string|null $title + * @param string|null $alt + * @param string|null $class + * @param string|null $id + * @param bool $reset + * @return string + */ + public function html($title = null, $alt = null, $class = null, $id = null, $reset = true) + { + return $this->bubble('html', [$title, $alt, $class, $id, $reset]); + } + + /** + * Switch display mode. + * + * @param string $mode + * + * @return MediaLinkInterface|MediaObjectInterface|null + */ + public function display($mode = 'source') + { + return $this->bubble('display', [$mode], false); + } + + /** + * Switch thumbnail. + * + * @param string $type + * + * @return MediaLinkInterface|MediaObjectInterface + */ + public function thumbnail($type = 'auto') + { + $this->bubble('thumbnail', [$type], false); + + return $this->bubble('getThumbnail', [], false); + } + + /** + * Turn the current Medium into a Link + * + * @param bool $reset + * @param array $attributes + * @return MediaLinkInterface + */ + public function link($reset = true, array $attributes = []) + { + return $this->bubble('link', [$reset, $attributes], false); + } + + /** + * Turn the current Medium into a Link with lightbox enabled + * + * @param int|null $width + * @param int|null $height + * @param bool $reset + * @return MediaLinkInterface + */ + public function lightbox($width = null, $height = null, $reset = true) + { + return $this->bubble('lightbox', [$width, $height, $reset], false); + } + + /** + * Bubble a function call up to either the superclass function or the parent Medium instance + * + * @param string $method + * @param array $arguments + * @param bool $testLinked + * @return mixed + */ + protected function bubble($method, array $arguments = [], $testLinked = true) + { + if (!$testLinked || $this->linked) { + $parent = $this->parent; + if (null === $parent) { + return $this; + } + + $closure = [$parent, $method]; + + if (!is_callable($closure)) { + throw new BadMethodCallException(get_class($parent) . '::' . $method . '() not found.'); + } + + return $closure(...$arguments); + } + + return parent::{$method}(...$arguments); + } +} diff --git a/system/src/Grav/Common/Media/Traits/VideoMediaTrait.php b/system/src/Grav/Common/Media/Traits/VideoMediaTrait.php new file mode 100644 index 0000000..101f42a --- /dev/null +++ b/system/src/Grav/Common/Media/Traits/VideoMediaTrait.php @@ -0,0 +1,68 @@ +attributes['poster'] = $urlImage; + + return $this; + } + + /** + * Allows to set the playsinline attribute + * + * @param bool $status + * @return $this + */ + public function playsinline($status = false) + { + if ($status) { + $this->attributes['playsinline'] = 'playsinline'; + } else { + unset($this->attributes['playsinline']); + } + + return $this; + } + + /** + * Parsedown element for source display mode + * + * @param array $attributes + * @param bool $reset + * @return array + */ + protected function sourceParsedownElement(array $attributes, $reset = true) + { + $location = $this->url($reset); + + return [ + 'name' => 'video', + 'rawHtml' => 'Your browser does not support the video tag.', + 'attributes' => $attributes + ]; + } +} diff --git a/system/src/Grav/Common/Page/Collection.php b/system/src/Grav/Common/Page/Collection.php new file mode 100644 index 0000000..73f52e6 --- /dev/null +++ b/system/src/Grav/Common/Page/Collection.php @@ -0,0 +1,710 @@ + + */ +class Collection extends Iterator implements PageCollectionInterface +{ + /** @var Pages */ + protected $pages; + /** @var array */ + protected $params; + + /** + * Collection constructor. + * + * @param array $items + * @param array $params + * @param Pages|null $pages + */ + public function __construct($items = [], array $params = [], Pages $pages = null) + { + parent::__construct($items); + + $this->params = $params; + $this->pages = $pages ?: Grav::instance()->offsetGet('pages'); + } + + /** + * Get the collection params + * + * @return array + */ + 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; + } + + /** + * Add a single page to a collection + * + * @param PageInterface $page + * @return $this + */ + public function addPage(PageInterface $page) + { + $this->items[$page->path()] = ['slug' => $page->slug()]; + + return $this; + } + + /** + * Add a page with path and slug + * + * @param string $path + * @param string $slug + * @return $this + */ + public function add($path, $slug) + { + $this->items[$path] = ['slug' => $slug]; + + return $this; + } + + /** + * + * Create a copy of this collection + * + * @return static + */ + public function copy() + { + return new static($this->items, $this->params, $this->pages); + } + + /** + * + * Merge another collection with the current collection + * + * @param PageCollectionInterface $collection + * @return $this + */ + public function merge(PageCollectionInterface $collection) + { + foreach ($collection as $page) { + $this->addPage($page); + } + + return $this; + } + + /** + * Intersect another collection with the current collection + * + * @param PageCollectionInterface $collection + * @return $this + */ + public function intersect(PageCollectionInterface $collection) + { + $array1 = $this->items; + $array2 = $collection->toArray(); + + $this->items = array_uintersect($array1, $array2, function ($val1, $val2) { + return strcmp($val1['slug'], $val2['slug']); + }); + + return $this; + } + + /** + * Set current page. + */ + public function setCurrent(string $path): void + { + reset($this->items); + + while (($key = key($this->items)) !== null && $key !== $path) { + next($this->items); + } + } + + /** + * Returns current page. + * + * @return PageInterface + */ + #[\ReturnTypeWillChange] + public function current() + { + $current = parent::key(); + + return $this->pages->get($current); + } + + /** + * Returns current slug. + * + * @return mixed + */ + #[\ReturnTypeWillChange] + public function key() + { + $current = parent::current(); + + return $current['slug']; + } + + /** + * Returns the value at specified offset. + * + * @param string $offset + * @return PageInterface|null + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return $this->pages->get($offset) ?: null; + } + + /** + * Split collection into array of smaller collections. + * + * @param int $size + * @return Collection[] + */ + public function batch($size) + { + $chunks = array_chunk($this->items, $size, true); + + $list = []; + foreach ($chunks as $chunk) { + $list[] = new static($chunk, $this->params, $this->pages); + } + + return $list; + } + + /** + * Remove item from the list. + * + * @param PageInterface|string|null $key + * @return $this + * @throws InvalidArgumentException + */ + public function remove($key = null) + { + if ($key instanceof PageInterface) { + $key = $key->path(); + } elseif (null === $key) { + $key = (string)key($this->items); + } + if (!is_string($key)) { + throw new InvalidArgumentException('Invalid argument $key.'); + } + + parent::remove($key); + + return $this; + } + + /** + * Reorder collection. + * + * @param string $by + * @param string $dir + * @param array|null $manual + * @param string|null $sort_flags + * @return $this + */ + public function order($by, $dir = 'asc', $manual = null, $sort_flags = null) + { + $this->items = $this->pages->sortCollection($this, $by, $dir, $manual, $sort_flags); + + return $this; + } + + /** + * Check to see if this item is the first in the collection. + * + * @param string $path + * @return bool True if item is first. + */ + public function isFirst($path): bool + { + return $this->items && $path === array_keys($this->items)[0]; + } + + /** + * Check to see if this item is the last in the collection. + * + * @param string $path + * @return bool True if item is last. + */ + public function isLast($path): bool + { + return $this->items && $path === array_keys($this->items)[count($this->items) - 1]; + } + + /** + * Gets the previous sibling based on current position. + * + * @param string $path + * + * @return PageInterface The previous item. + */ + public function prevSibling($path) + { + return $this->adjacentSibling($path, -1); + } + + /** + * Gets the next sibling based on current position. + * + * @param string $path + * + * @return PageInterface The next item. + */ + public function nextSibling($path) + { + return $this->adjacentSibling($path, 1); + } + + /** + * Returns the adjacent sibling based on a direction. + * + * @param string $path + * @param int $direction either -1 or +1 + * @return PageInterface|Collection The sibling item. + */ + public function adjacentSibling($path, $direction = 1) + { + $values = array_keys($this->items); + $keys = array_flip($values); + + if (array_key_exists($path, $keys)) { + $index = $keys[$path] - $direction; + + return isset($values[$index]) ? $this->offsetGet($values[$index]) : $this; + } + + return $this; + } + + /** + * Returns the item in the current position. + * + * @param string $path the path the item + * @return int|null The index of the current page, null if not found. + */ + public function currentPosition($path): ?int + { + $pos = array_search($path, array_keys($this->items), true); + + return $pos !== false ? $pos : null; + } + + /** + * Returns the items between a set of date ranges of either the page date field (default) or + * an arbitrary datetime page field where start date and end date are optional + * Dates must be passed in as text that strtotime() can process + * http://php.net/manual/en/function.strtotime.php + * + * @param string|null $startDate + * @param string|null $endDate + * @param string|null $field + * @return $this + * @throws Exception + */ + public function dateRange($startDate = null, $endDate = null, $field = null) + { + $start = $startDate ? Utils::date2timestamp($startDate) : null; + $end = $endDate ? Utils::date2timestamp($endDate) : null; + + $date_range = []; + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + if (!$page) { + continue; + } + + $date = $field ? strtotime($page->value($field)) : $page->date(); + + if ((!$start || $date >= $start) && (!$end || $date <= $end)) { + $date_range[$path] = $slug; + } + } + + $this->items = $date_range; + + return $this; + } + + /** + * Creates new collection with only visible pages + * + * @return Collection The collection with only visible pages + */ + public function visible() + { + $visible = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + if ($page !== null && $page->visible()) { + $visible[$path] = $slug; + } + } + $this->items = $visible; + + return $this; + } + + /** + * Creates new collection with only non-visible pages + * + * @return Collection The collection with only non-visible pages + */ + public function nonVisible() + { + $visible = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + if ($page !== null && !$page->visible()) { + $visible[$path] = $slug; + } + } + $this->items = $visible; + + return $this; + } + + /** + * Creates new collection with only pages + * + * @return Collection The collection with only pages + */ + public function pages() + { + $modular = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + if ($page !== null && !$page->isModule()) { + $modular[$path] = $slug; + } + } + $this->items = $modular; + + return $this; + } + + /** + * Creates new collection with only modules + * + * @return Collection The collection with only modules + */ + public function modules() + { + $modular = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + if ($page !== null && $page->isModule()) { + $modular[$path] = $slug; + } + } + $this->items = $modular; + + return $this; + } + + /** + * Alias of pages() + * + * @return Collection The collection with only non-module pages + */ + public function nonModular() + { + $this->pages(); + + return $this; + } + + /** + * Alias of modules() + * + * @return Collection The collection with only modules + */ + public function modular() + { + $this->modules(); + + return $this; + } + + /** + * Creates new collection with only translated pages + * + * @return Collection The collection with only published pages + * @internal + */ + public function translated() + { + $published = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + if ($page !== null && $page->translated()) { + $published[$path] = $slug; + } + } + $this->items = $published; + + return $this; + } + + /** + * Creates new collection with only untranslated pages + * + * @return Collection The collection with only non-published pages + * @internal + */ + public function nonTranslated() + { + $published = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + if ($page !== null && !$page->translated()) { + $published[$path] = $slug; + } + } + $this->items = $published; + + return $this; + } + + /** + * Creates new collection with only published pages + * + * @return Collection The collection with only published pages + */ + public function published() + { + $published = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + if ($page !== null && $page->published()) { + $published[$path] = $slug; + } + } + $this->items = $published; + + return $this; + } + + /** + * Creates new collection with only non-published pages + * + * @return Collection The collection with only non-published pages + */ + public function nonPublished() + { + $published = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + if ($page !== null && !$page->published()) { + $published[$path] = $slug; + } + } + $this->items = $published; + + return $this; + } + + /** + * Creates new collection with only routable pages + * + * @return Collection The collection with only routable pages + */ + public function routable() + { + $routable = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + + if ($page !== null && $page->routable()) { + $routable[$path] = $slug; + } + } + + $this->items = $routable; + + return $this; + } + + /** + * Creates new collection with only non-routable pages + * + * @return Collection The collection with only non-routable pages + */ + public function nonRoutable() + { + $routable = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + if ($page !== null && !$page->routable()) { + $routable[$path] = $slug; + } + } + $this->items = $routable; + + return $this; + } + + /** + * Creates new collection with only pages of the specified type + * + * @param string $type + * @return Collection The collection + */ + public function ofType($type) + { + $items = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + if ($page !== null && $page->template() === $type) { + $items[$path] = $slug; + } + } + + $this->items = $items; + + return $this; + } + + /** + * Creates new collection with only pages of one of the specified types + * + * @param string[] $types + * @return Collection The collection + */ + public function ofOneOfTheseTypes($types) + { + $items = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + if ($page !== null && in_array($page->template(), $types, true)) { + $items[$path] = $slug; + } + } + + $this->items = $items; + + return $this; + } + + /** + * Creates new collection with only pages of one of the specified access levels + * + * @param array $accessLevels + * @return Collection The collection + */ + public function ofOneOfTheseAccessLevels($accessLevels) + { + $items = []; + + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + + if ($page !== null && isset($page->header()->access)) { + if (is_array($page->header()->access)) { + //Multiple values for access + $valid = false; + + foreach ($page->header()->access as $index => $accessLevel) { + if (is_array($accessLevel)) { + foreach ($accessLevel as $innerIndex => $innerAccessLevel) { + if (in_array($innerAccessLevel, $accessLevels, false)) { + $valid = true; + } + } + } else { + if (in_array($index, $accessLevels, false)) { + $valid = true; + } + } + } + if ($valid) { + $items[$path] = $slug; + } + } else { + //Single value for access + if (in_array($page->header()->access, $accessLevels, false)) { + $items[$path] = $slug; + } + } + } + } + + $this->items = $items; + + return $this; + } + + /** + * Get the extended version of this Collection with each page keyed by route + * + * @return array + * @throws Exception + */ + public function toExtendedArray() + { + $items = []; + foreach ($this->items as $path => $slug) { + $page = $this->pages->get($path); + + if ($page !== null) { + $items[$page->route()] = $page->toArray(); + } + } + return $items; + } +} diff --git a/system/src/Grav/Common/Page/Header.php b/system/src/Grav/Common/Page/Header.php new file mode 100644 index 0000000..3dc9df6 --- /dev/null +++ b/system/src/Grav/Common/Page/Header.php @@ -0,0 +1,38 @@ +toArray(); + } +} diff --git a/system/src/Grav/Common/Page/Interfaces/PageCollectionInterface.php b/system/src/Grav/Common/Page/Interfaces/PageCollectionInterface.php new file mode 100644 index 0000000..83e147d --- /dev/null +++ b/system/src/Grav/Common/Page/Interfaces/PageCollectionInterface.php @@ -0,0 +1,310 @@ + + * @extends ArrayAccess + */ +interface PageCollectionInterface extends Traversable, ArrayAccess, Countable, Serializable +{ + /** + * Get the collection params + * + * @return array + */ + public function params(); + + /** + * Set parameters to the Collection + * + * @param array $params + * @return $this + */ + public function setParams(array $params); + + /** + * Add a single page to a collection + * + * @param PageInterface $page + * @return $this + */ + public function addPage(PageInterface $page); + + /** + * Add a page with path and slug + * + * @param string $path + * @param string $slug + * @return $this + */ + //public function add($path, $slug); + + /** + * + * Create a copy of this collection + * + * @return static + */ + public function copy(); + + /** + * + * Merge another collection with the current collection + * + * @param PageCollectionInterface $collection + * @return PageCollectionInterface + * @phpstan-return PageCollectionInterface + */ + public function merge(PageCollectionInterface $collection); + + /** + * Intersect another collection with the current collection + * + * @param PageCollectionInterface $collection + * @return PageCollectionInterface + * @phpstan-return PageCollectionInterface + */ + public function intersect(PageCollectionInterface $collection); + + /** + * Split collection into array of smaller collections. + * + * @param int $size + * @return PageCollectionInterface[] + * @phpstan-return array> + */ + public function batch($size); + + /** + * Remove item from the list. + * + * @param PageInterface|string|null $key + * @return PageCollectionInterface + * @phpstan-return PageCollectionInterface + * @throws InvalidArgumentException + */ + //public function remove($key = null); + + /** + * Reorder collection. + * + * @param string $by + * @param string $dir + * @param array|null $manual + * @param string|null $sort_flags + * @return PageCollectionInterface + * @phpstan-return PageCollectionInterface + */ + public function order($by, $dir = 'asc', $manual = null, $sort_flags = null); + + /** + * Check to see if this item is the first in the collection. + * + * @param string $path + * @return bool True if item is first. + */ + public function isFirst($path): bool; + + /** + * Check to see if this item is the last in the collection. + * + * @param string $path + * @return bool True if item is last. + */ + public function isLast($path): bool; + + /** + * Gets the previous sibling based on current position. + * + * @param string $path + * @return PageInterface The previous item. + * @phpstan-return T + */ + public function prevSibling($path); + + /** + * Gets the next sibling based on current position. + * + * @param string $path + * @return PageInterface The next item. + * @phpstan-return T + */ + public function nextSibling($path); + + /** + * Returns the adjacent sibling based on a direction. + * + * @param string $path + * @param int $direction either -1 or +1 + * @return PageInterface|PageCollectionInterface|false The sibling item. + * @phpstan-return T|false + */ + public function adjacentSibling($path, $direction = 1); + + /** + * Returns the item in the current position. + * + * @param string $path the path the item + * @return int|null The index of the current page, null if not found. + */ + public function currentPosition($path): ?int; + + /** + * Returns the items between a set of date ranges of either the page date field (default) or + * an arbitrary datetime page field where start date and end date are optional + * Dates must be passed in as text that strtotime() can process + * http://php.net/manual/en/function.strtotime.php + * + * @param string|null $startDate + * @param string|null $endDate + * @param string|null $field + * @return PageCollectionInterface + * @phpstan-return PageCollectionInterface + * @throws Exception + */ + public function dateRange($startDate = null, $endDate = null, $field = null); + + /** + * Creates new collection with only visible pages + * + * @return PageCollectionInterface The collection with only visible pages + * @phpstan-return PageCollectionInterface + */ + public function visible(); + + /** + * Creates new collection with only non-visible pages + * + * @return PageCollectionInterface The collection with only non-visible pages + * @phpstan-return PageCollectionInterface + */ + public function nonVisible(); + + /** + * Creates new collection with only pages + * + * @return PageCollectionInterface The collection with only pages + * @phpstan-return PageCollectionInterface + */ + public function pages(); + + /** + * Creates new collection with only modules + * + * @return PageCollectionInterface The collection with only modules + * @phpstan-return PageCollectionInterface + */ + public function modules(); + + /** + * Creates new collection with only modules + * + * @return PageCollectionInterface The collection with only modules + * @phpstan-return PageCollectionInterface + * @deprecated 1.7 Use $this->modules() instead + */ + public function modular(); + + /** + * Creates new collection with only non-module pages + * + * @return PageCollectionInterface The collection with only non-module pages + * @phpstan-return PageCollectionInterface + * @deprecated 1.7 Use $this->pages() instead + */ + public function nonModular(); + + /** + * Creates new collection with only published pages + * + * @return PageCollectionInterface The collection with only published pages + * @phpstan-return PageCollectionInterface + */ + public function published(); + + /** + * Creates new collection with only non-published pages + * + * @return PageCollectionInterface The collection with only non-published pages + * @phpstan-return PageCollectionInterface + */ + public function nonPublished(); + + /** + * Creates new collection with only routable pages + * + * @return PageCollectionInterface The collection with only routable pages + * @phpstan-return PageCollectionInterface + */ + public function routable(); + + /** + * Creates new collection with only non-routable pages + * + * @return PageCollectionInterface The collection with only non-routable pages + * @phpstan-return PageCollectionInterface + */ + public function nonRoutable(); + + /** + * Creates new collection with only pages of the specified type + * + * @param string $type + * @return PageCollectionInterface The collection + * @phpstan-return PageCollectionInterface + */ + public function ofType($type); + + /** + * Creates new collection with only pages of one of the specified types + * + * @param string[] $types + * @return PageCollectionInterface The collection + * @phpstan-return PageCollectionInterface + */ + public function ofOneOfTheseTypes($types); + + /** + * Creates new collection with only pages of one of the specified access levels + * + * @param array $accessLevels + * @return PageCollectionInterface The collection + * @phpstan-return PageCollectionInterface + */ + public function ofOneOfTheseAccessLevels($accessLevels); + + /** + * Converts collection into an array. + * + * @return array + */ + public function toArray(); + + /** + * Get the extended version of this Collection with each page keyed by route + * + * @return array + * @throws Exception + */ + public function toExtendedArray(); +} diff --git a/system/src/Grav/Common/Page/Interfaces/PageContentInterface.php b/system/src/Grav/Common/Page/Interfaces/PageContentInterface.php new file mode 100644 index 0000000..c620941 --- /dev/null +++ b/system/src/Grav/Common/Page/Interfaces/PageContentInterface.php @@ -0,0 +1,267 @@ +true) for example + * + * @param array|null $var New array of name value pairs where the name is the process and value is true or false + * @return array Array of name value pairs where the name is the process and value is true or false + */ + public function process($var = null); + + /** + * 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|null $var New slug, e.g. 'my-blog' + * @return string The slug + */ + public function slug($var = null); + + /** + * Get/set order number of this page. + * + * @param int|null $var New order as a number + * @return string|bool Order in a form of '02.' or false if not set + */ + public function order($var = null); + + /** + * Gets and sets the identifier for this Page object. + * + * @param string|null $var New identifier + * @return string The identifier + */ + public function id($var = null); + + /** + * Gets and sets the modified timestamp. + * + * @param int|null $var New modified unix timestamp + * @return int Modified unix timestamp + */ + public function modified($var = null); + + /** + * Gets and sets the option to show the last_modified header for the page. + * + * @param bool|null $var New last_modified header value + * @return bool Show last_modified header + */ + public function lastModified($var = null); + + /** + * Get/set the folder. + * + * @param string|null $var New folder + * @return string|null The folder + */ + public function folder($var = null); + + /** + * Gets and sets the date for this Page object. This is typically passed in via the page headers + * + * @param string|null $var New string representation of a date + * @return int Unix timestamp representation of the date + */ + public function date($var = null); + + /** + * Gets and sets the date format for this Page object. This is typically passed in via the page headers + * using typical PHP date string structure - http://php.net/manual/en/function.date.php + * + * @param string|null $var New string representation of a date format + * @return string String representation of a date format + */ + public function dateformat($var = null); + + /** + * Gets and sets the taxonomy array which defines which taxonomies this page identifies itself with. + * + * @param array|null $var New array of taxonomies + * @return array An array of taxonomies + */ + public function taxonomy($var = null); + + /** + * Gets the configured state of the processing method. + * + * @param string $process The process name, eg "twig" or "markdown" + * @return bool Whether or not the processing method is enabled for this Page + */ + public function shouldProcess($process); + + /** + * Returns true if page is a module. + * + * @return bool + */ + public function isModule(): bool; + + /** + * 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(); + + /** + * Returns whether or not this Page object is a directory or a page. + * + * @return bool True if its a directory + */ + public function isDir(); + + /** + * Returns whether the page exists in the filesystem. + * + * @return bool + */ + public function exists(); + + /** + * Returns the blueprint from the page. + * + * @param string $name Name of the Blueprint form. Used by flex only. + * @return Blueprint Returns a Blueprint. + */ + public function getBlueprint(string $name = ''); +} diff --git a/system/src/Grav/Common/Page/Interfaces/PageFormInterface.php b/system/src/Grav/Common/Page/Interfaces/PageFormInterface.php new file mode 100644 index 0000000..3c88ebf --- /dev/null +++ b/system/src/Grav/Common/Page/Interfaces/PageFormInterface.php @@ -0,0 +1,33 @@ + blueprint, ...], where blueprint follows the regular form blueprint format. + * + * @return array + */ + //public function getForms(): array; + + /** + * Add forms to this page. + * + * @param array $new + * @return $this + */ + public function addForms(array $new/*, $override = true*/); + + /** + * Alias of $this->getForms(); + * + * @return array + */ + public function forms();//: array; +} diff --git a/system/src/Grav/Common/Page/Interfaces/PageInterface.php b/system/src/Grav/Common/Page/Interfaces/PageInterface.php new file mode 100644 index 0000000..b48e5be --- /dev/null +++ b/system/src/Grav/Common/Page/Interfaces/PageInterface.php @@ -0,0 +1,25 @@ +save() in order to perform the move. + * + * @param PageInterface $parent New parent page. + * @return $this + */ + public function move(PageInterface $parent); + + /** + * 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 PageInterface $parent New parent page. + * @return $this + */ + public function copy(PageInterface $parent); + + /** + * Get blueprints for the page. + * + * @return Blueprint + */ + public function blueprints(); + + /** + * Get the blueprint name for this page. Use the blueprint form field if set + * + * @return string + */ + public function blueprintName(); + + /** + * Validate page header. + * + * @throws Exception + */ + public function validate(); + + /** + * Filter page header from illegal contents. + */ + public function filter(); + + /** + * Get unknown header variables. + * + * @return array + */ + public function extra(); + + /** + * Convert page to an array. + * + * @return array + */ + public function toArray(); + + /** + * Convert page to YAML encoded string. + * + * @return string + */ + public function toYaml(); + + /** + * Convert page to JSON encoded string. + * + * @return string + */ + public function toJson(); + + /** + * Returns normalized list of name => form pairs. + * + * @return array + */ + public function forms(); + + /** + * @param array $new + */ + public function addForms(array $new); + + /** + * Gets and sets the name field. If no name field is set, it will return 'default.md'. + * + * @param string|null $var The name of this page. + * @return string The name of this page. + */ + public function name($var = null); + + /** + * Returns child page type. + * + * @return string + */ + public function childType(); + + /** + * 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|null $var the template name + * @return string the template name + */ + public function template($var = null); + + /** + * Allows a page to override the output render format, usually the extension provided + * in the URL. (e.g. `html`, `json`, `xml`, etc). + * + * @param string|null $var + * @return string + */ + public function templateFormat($var = null); + + /** + * Gets and sets the extension field. + * + * @param string|null $var + * @return string|null + */ + public function extension($var = null); + + /** + * Gets and sets the expires field. If not set will return the default + * + * @param int|null $var The new expires value. + * @return int The expires value + */ + public function expires($var = null); + + /** + * Gets and sets the cache-control property. If not set it will return the default value (null) + * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control for more details on valid options + * + * @param string|null $var + * @return string|null + */ + public function cacheControl($var = null); + + /** + * @param bool|null $var + * @return bool + */ + public function ssl($var = null); + + /** + * Returns the state of the debugger override etting for this page + * + * @return bool + */ + public function debugger(); + + /** + * Function to merge page metadata tags and build an array of Metadata objects + * that can then be rendered in the page. + * + * @param array|null $var an Array of metadata values to set + * @return array an Array of metadata values for the page + */ + public function metadata($var = null); + + /** + * Gets and sets the option to show the etag header for the page. + * + * @param bool|null $var show etag header + * @return bool show etag header + */ + public function eTag($var = null): bool; + + /** + * Gets and sets the path to the .md file for this Page object. + * + * @param string|null $var the file path + * @return string|null the file path + */ + public function filePath($var = null); + + /** + * Gets the relative path to the .md file + * + * @return string The relative file path + */ + public function filePathClean(); + + /** + * Gets and sets the order by which any sub-pages should be sorted. + * + * @param string|null $var the order, either "asc" or "desc" + * @return string the order, either "asc" or "desc" + * @deprecated 1.6 + */ + public function orderDir($var = null); + + /** + * 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|null $var supported options include "default", "title", "date", and "folder" + * @return string supported options include "default", "title", "date", and "folder" + * @deprecated 1.6 + */ + public function orderBy($var = null); + + /** + * Gets the manual order set in the header. + * + * @param string|null $var supported options include "default", "title", "date", and "folder" + * @return array + * @deprecated 1.6 + */ + public function orderManual($var = null); + + /** + * 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|null $var the maximum number of sub-pages + * @return int the maximum number of sub-pages + * @deprecated 1.6 + */ + public function maxCount($var = null); + + /** + * Gets and sets the modular var that helps identify this page is a modular child + * + * @param bool|null $var true if modular_twig + * @return bool true if modular_twig + * @deprecated 1.7 Use ->isModule() or ->modularTwig() method instead. + */ + public function modular($var = null); + + /** + * Gets and sets the modular_twig var that helps identify this page as a modular child page that will need + * twig processing handled differently from a regular page. + * + * @param bool|null $var true if modular_twig + * @return bool true if modular_twig + */ + public function modularTwig($var = null); + + /** + * Returns children of this page. + * + * @return PageCollectionInterface|Collection + */ + public function children(); + + /** + * Check to see if this item is the first in an array of sub-pages. + * + * @return bool True if item is first. + */ + public function isFirst(); + + /** + * Check to see if this item is the last in an array of sub-pages. + * + * @return bool True if item is last + */ + public function isLast(); + + /** + * Gets the previous sibling based on current position. + * + * @return PageInterface the previous Page item + */ + public function prevSibling(); + + /** + * Gets the next sibling based on current position. + * + * @return PageInterface the next Page item + */ + public function nextSibling(); + + /** + * Returns the adjacent sibling based on a direction. + * + * @param int $direction either -1 or +1 + * @return PageInterface|false the sibling page + */ + public function adjacentSibling($direction = 1); + + /** + * Helper method to return an ancestor page. + * + * @param bool|null $lookup Name of the parent folder + * @return PageInterface page you were looking for if it exists + */ + public function ancestor($lookup = null); + + /** + * Helper method to return an ancestor page to inherit from. The current + * page object is returned. + * + * @param string $field Name of the parent folder + * @return PageInterface + */ + public function inherited($field); + + /** + * Helper method to return an ancestor field only to inherit from. The + * first occurrence of an ancestor field will be returned if at all. + * + * @param string $field Name of the parent folder + * @return array + */ + public function inheritedField($field); + + /** + * Helper method to return a page. + * + * @param string $url the url of the page + * @param bool $all + * @return PageInterface page you were looking for if it exists + */ + public function find($url, $all = false); + + /** + * Get a collection of pages in the current context. + * + * @param string|array $params + * @param bool $pagination + * @return Collection + * @throws InvalidArgumentException + */ + public function collection($params = 'content', $pagination = true); + + /** + * @param string|array $value + * @param bool $only_published + * @return PageCollectionInterface|Collection + */ + public function evaluate($value, $only_published = true); + + /** + * Returns whether or not the current folder exists + * + * @return bool + */ + public function folderExists(); + + /** + * Gets the Page Unmodified (original) version of the page. + * + * @return PageInterface The original version of the page. + */ + public function getOriginal(); + + /** + * Gets the action. + * + * @return string The Action string. + */ + public function getAction(); +} diff --git a/system/src/Grav/Common/Page/Interfaces/PageRoutableInterface.php b/system/src/Grav/Common/Page/Interfaces/PageRoutableInterface.php new file mode 100644 index 0000000..2900266 --- /dev/null +++ b/system/src/Grav/Common/Page/Interfaces/PageRoutableInterface.php @@ -0,0 +1,180 @@ +page = $page ?? Grav::instance()['page'] ?? null; + + // Add defaults to the configuration. + if (null === $config || !isset($config['markdown'], $config['images'])) { + $c = Grav::instance()['config']; + $config = $config ?? []; + $config += [ + 'markdown' => $c->get('system.pages.markdown', []), + 'images' => $c->get('system.images', []) + ]; + } + + $this->config = $config; + } + + /** + * @return PageInterface|null + */ + public function getPage(): ?PageInterface + { + return $this->page; + } + + /** + * @return array + */ + public function getConfig(): array + { + return $this->config; + } + + /** + * @param object $markdown + * @return void + */ + public function fireInitializedEvent($markdown): void + { + $grav = Grav::instance(); + + $grav->fireEvent('onMarkdownInitialized', new Event(['markdown' => $markdown, 'page' => $this->page])); + } + + /** + * Process a Link excerpt + * + * @param array $excerpt + * @param string $type + * @return array + */ + public function processLinkExcerpt(array $excerpt, string $type = 'link'): array + { + $grav = Grav::instance(); + $url = htmlspecialchars_decode(rawurldecode($excerpt['element']['attributes']['href'])); + $url_parts = $this->parseUrl($url); + + // If there is a query, then parse it and build action calls. + if (isset($url_parts['query'])) { + $actions = array_reduce( + explode('&', $url_parts['query']), + static function ($carry, $item) { + $parts = explode('=', $item, 2); + $value = isset($parts[1]) ? rawurldecode($parts[1]) : true; + $carry[$parts[0]] = $value; + + return $carry; + }, + [] + ); + + // Valid attributes supported. + $valid_attributes = $grav['config']->get('system.pages.markdown.valid_link_attributes') ?? []; + + $skip = []; + // Unless told to not process, go through actions. + if (array_key_exists('noprocess', $actions)) { + $skip = is_bool($actions['noprocess']) ? $actions : explode(',', $actions['noprocess']); + unset($actions['noprocess']); + } + + // Loop through actions for the image and call them. + foreach ($actions as $attrib => $value) { + if (!in_array($attrib, $skip)) { + $key = $attrib; + + if (in_array($attrib, $valid_attributes, true)) { + // support both class and classes. + if ($attrib === 'classes') { + $attrib = 'class'; + } + $excerpt['element']['attributes'][$attrib] = str_replace(',', ' ', $value); + unset($actions[$key]); + } + } + } + + $url_parts['query'] = http_build_query($actions, '', '&', PHP_QUERY_RFC3986); + } + + // If no query elements left, unset query. + if (empty($url_parts['query'])) { + unset($url_parts['query']); + } + + // Set path to / if not set. + if (empty($url_parts['path'])) { + $url_parts['path'] = ''; + } + + // If scheme isn't http(s).. + if (!empty($url_parts['scheme']) && !in_array($url_parts['scheme'], ['http', 'https'])) { + // Handle custom streams. + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + if ($type === 'link' && $locator->isStream($url)) { + $path = $locator->findResource($url, false) ?: $locator->findResource($url, false, true); + $url_parts['path'] = $grav['base_url_relative'] . '/' . $path; + unset($url_parts['stream'], $url_parts['scheme']); + } + + $excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts); + + return $excerpt; + } + + // Handle paths and such. + $url_parts = Uri::convertUrl($this->page, $url_parts, $type); + + // Build the URL from the component parts and set it on the element. + $excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts); + + return $excerpt; + } + + /** + * Process an image excerpt + * + * @param array $excerpt + * @return array + */ + public function processImageExcerpt(array $excerpt): array + { + $url = htmlspecialchars_decode(urldecode($excerpt['element']['attributes']['src'])); + $url_parts = $this->parseUrl($url); + + $media = null; + $filename = null; + + if (!empty($url_parts['stream'])) { + $filename = $url_parts['scheme'] . '://' . ($url_parts['path'] ?? ''); + + $media = $this->page->getMedia(); + } else { + $grav = Grav::instance(); + /** @var Pages $pages */ + $pages = $grav['pages']; + + // File is also local if scheme is http(s) and host matches. + $local_file = isset($url_parts['path']) + && (empty($url_parts['scheme']) || in_array($url_parts['scheme'], ['http', 'https'], true)) + && (empty($url_parts['host']) || $url_parts['host'] === $grav['uri']->host()); + + if ($local_file) { + $filename = Utils::basename($url_parts['path']); + $folder = dirname($url_parts['path']); + + // Get the local path to page media if possible. + if ($this->page && $folder === $this->page->url(false, false, false)) { + // Get the media objects for this page. + $media = $this->page->getMedia(); + } else { + // see if this is an external page to this one + $base_url = rtrim($grav['base_url_relative'] . $pages->base(), '/'); + $page_route = '/' . ltrim(str_replace($base_url, '', $folder), '/'); + + $ext_page = $pages->find($page_route, true); + if ($ext_page) { + $media = $ext_page->getMedia(); + } else { + $grav->fireEvent('onMediaLocate', new Event(['route' => $page_route, 'media' => &$media])); + } + } + } + } + + // If there is a media file that matches the path referenced.. + if ($media && $filename && isset($media[$filename])) { + // Get the medium object. + /** @var Medium $medium */ + $medium = $media[$filename]; + + // Process operations + $medium = $this->processMediaActions($medium, $url_parts); + $element_excerpt = $excerpt['element']['attributes']; + + $alt = $element_excerpt['alt'] ?? ''; + $title = $element_excerpt['title'] ?? ''; + $class = $element_excerpt['class'] ?? ''; + $id = $element_excerpt['id'] ?? ''; + + $excerpt['element'] = $medium->parsedownElement($title, $alt, $class, $id, true); + } else { + // Not a current page media file, see if it needs converting to relative. + $excerpt['element']['attributes']['src'] = Uri::buildUrl($url_parts); + } + + return $excerpt; + } + + /** + * Process media actions + * + * @param Medium $medium + * @param string|array $url + * @return Medium|Link + */ + public function processMediaActions($medium, $url) + { + $url_parts = is_string($url) ? $this->parseUrl($url) : $url; + $actions = []; + + + // if there is a query, then parse it and build action calls + if (isset($url_parts['query'])) { + $actions = array_reduce( + explode('&', $url_parts['query']), + static function ($carry, $item) { + $parts = explode('=', $item, 2); + $value = $parts[1] ?? null; + $carry[] = ['method' => $parts[0], 'params' => $value]; + + return $carry; + }, + [] + ); + } + + $defaults = $this->config['images']['defaults'] ?? []; + if (count($defaults)) { + foreach ($defaults as $method => $params) { + if (array_search($method, array_column($actions, 'method')) === false) { + $actions[] = [ + 'method' => $method, + 'params' => $params, + ]; + } + } + } + + // loop through actions for the image and call them + foreach ($actions as $action) { + $matches = []; + + if (preg_match('/\[(.*)\]/', $action['params'], $matches)) { + $args = [explode(',', $matches[1])]; + } else { + $args = explode(',', $action['params']); + } + + $medium = call_user_func_array([$medium, $action['method']], $args); + } + + if (isset($url_parts['fragment'])) { + $medium->urlHash($url_parts['fragment']); + } + + return $medium; + } + + /** + * Variation of parse_url() which works also with local streams. + * + * @param string $url + * @return array + */ + protected function parseUrl(string $url) + { + $url_parts = Utils::multibyteParseUrl($url); + + if (isset($url_parts['scheme'])) { + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + + // Special handling for the streams. + if ($locator->schemeExists($url_parts['scheme'])) { + if (isset($url_parts['host'])) { + // Merge host and path into a path. + $url_parts['path'] = $url_parts['host'] . (isset($url_parts['path']) ? '/' . $url_parts['path'] : ''); + unset($url_parts['host']); + } + + $url_parts['stream'] = true; + } + } + + return $url_parts; + } +} diff --git a/system/src/Grav/Common/Page/Media.php b/system/src/Grav/Common/Page/Media.php new file mode 100644 index 0000000..12c399c --- /dev/null +++ b/system/src/Grav/Common/Page/Media.php @@ -0,0 +1,286 @@ +setPath($path); + $this->media_order = $media_order; + + $this->__wakeup(); + if ($load) { + $this->init(); + } + } + + /** + * Initialize static variables on unserialize. + */ + public function __wakeup() + { + if (null === static::$global) { + // Add fallback to global media. + static::$global = GlobalMedia::getInstance(); + } + } + + /** + * Return raw route to the page. + * + * @return string|null Route to the page or null if media isn't for a page. + */ + public function getRawRoute(): ?string + { + $path = $this->getPath(); + if ($path) { + /** @var Pages $pages */ + $pages = $this->getGrav()['pages']; + $page = $pages->get($path); + if ($page) { + return $page->rawRoute(); + } + } + + return null; + } + + /** + * Return page route. + * + * @return string|null Route to the page or null if media isn't for a page. + */ + public function getRoute(): ?string + { + $path = $this->getPath(); + if ($path) { + /** @var Pages $pages */ + $pages = $this->getGrav()['pages']; + $page = $pages->get($path); + if ($page) { + return $page->route(); + } + } + + return null; + } + + /** + * @param string $offset + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($offset) + { + return parent::offsetExists($offset) ?: isset(static::$global[$offset]); + } + + /** + * @param string $offset + * @return MediaObjectInterface|null + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return parent::offsetGet($offset) ?: static::$global[$offset]; + } + + /** + * Initialize class. + * + * @return void + */ + protected function init() + { + $path = $this->getPath(); + + // Handle special cases where page doesn't exist in filesystem. + if (!$path || !is_dir($path)) { + return; + } + + $grav = Grav::instance(); + + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + + /** @var Config $config */ + $config = $grav['config']; + + $exif_reader = isset($grav['exif']) ? $grav['exif']->getReader() : null; + $media_types = array_keys($config->get('media.types', [])); + + $iterator = new FilesystemIterator($path, FilesystemIterator::UNIX_PATHS | FilesystemIterator::SKIP_DOTS); + + $media = []; + + foreach ($iterator as $file => $info) { + // Ignore folders and Markdown files. + $filename = $info->getFilename(); + if (!$info->isFile() || $info->getExtension() === 'md' || $filename === 'frontmatter.yaml' || $filename === 'media.json' || strpos($filename, '.') === 0) { + continue; + } + + // Find out what type we're dealing with + [$basename, $ext, $type, $extra] = $this->getFileParts($filename); + + if (!in_array(strtolower($ext), $media_types, true)) { + continue; + } + + if ($type === 'alternative') { + $media["{$basename}.{$ext}"][$type][$extra] = ['file' => $file, 'size' => $info->getSize()]; + } else { + $media["{$basename}.{$ext}"][$type] = ['file' => $file, 'size' => $info->getSize()]; + } + } + + foreach ($media as $name => $types) { + // First prepare the alternatives in case there is no base medium + if (!empty($types['alternative'])) { + /** + * @var string|int $ratio + * @var array $alt + */ + foreach ($types['alternative'] as $ratio => &$alt) { + $alt['file'] = $this->createFromFile($alt['file']); + + if (empty($alt['file'])) { + unset($types['alternative'][$ratio]); + } else { + $alt['file']->set('size', $alt['size']); + } + } + unset($alt); + } + + $file_path = null; + + // Create the base medium + if (empty($types['base'])) { + if (!isset($types['alternative'])) { + continue; + } + + $max = max(array_keys($types['alternative'])); + $medium = $types['alternative'][$max]['file']; + $file_path = $medium->path(); + $medium = MediumFactory::scaledFromMedium($medium, $max, 1)['file']; + } else { + $medium = $this->createFromFile($types['base']['file']); + if ($medium) { + $medium->set('size', $types['base']['size']); + $file_path = $medium->path(); + } + } + + if (empty($medium)) { + continue; + } + + // metadata file + $meta_path = $file_path . '.meta.yaml'; + + if (file_exists($meta_path)) { + $types['meta']['file'] = $meta_path; + } elseif ($file_path && $exif_reader && $medium->get('mime') === 'image/jpeg' && empty($types['meta']) && $config->get('system.media.auto_metadata_exif')) { + $meta = $exif_reader->read($file_path); + + if ($meta) { + $meta_data = $meta->getData(); + $meta_trimmed = array_diff_key($meta_data, array_flip($this->standard_exif)); + if ($meta_trimmed) { + if ($locator->isStream($meta_path)) { + $file = File::instance($locator->findResource($meta_path, true, true)); + } else { + $file = File::instance($meta_path); + } + $file->save(Yaml::dump($meta_trimmed)); + $types['meta']['file'] = $meta_path; + } + } + } + + if (!empty($types['meta'])) { + $medium->addMetaFile($types['meta']['file']); + } + + if (!empty($types['thumb'])) { + // We will not turn it into medium yet because user might never request the thumbnail + // not wasting any resources on that, maybe we should do this for medium in general? + $medium->set('thumbnails.page', $types['thumb']['file']); + } + + // Build missing alternatives + if (!empty($types['alternative'])) { + $alternatives = $types['alternative']; + $max = max(array_keys($alternatives)); + + for ($i=$max; $i > 1; $i--) { + if (isset($alternatives[$i])) { + continue; + } + + $types['alternative'][$i] = MediumFactory::scaledFromMedium($alternatives[$max]['file'], $max, $i); + } + + foreach ($types['alternative'] as $altMedium) { + if ($altMedium['file'] != $medium) { + $altWidth = $altMedium['file']->get('width'); + $medWidth = $medium->get('width'); + if ($altWidth && $medWidth) { + $ratio = $altWidth / $medWidth; + $medium->addAlternative($ratio, $altMedium['file']); + } + } + } + } + + $this->add($name, $medium); + } + } + + /** + * @return string|null + * @deprecated 1.6 Use $this->getPath() instead. + */ + public function path(): ?string + { + return $this->getPath(); + } +} diff --git a/system/src/Grav/Common/Page/Medium/AbstractMedia.php b/system/src/Grav/Common/Page/Medium/AbstractMedia.php new file mode 100644 index 0000000..3f79e4c --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/AbstractMedia.php @@ -0,0 +1,344 @@ +path; + } + + /** + * @param string|null $path + * @return void + */ + public function setPath(?string $path): void + { + $this->path = $path; + } + + /** + * Get medium by filename. + * + * @param string $filename + * @return MediaObjectInterface|null + */ + public function get($filename) + { + return $this->offsetGet($filename); + } + + /** + * Call object as function to get medium by filename. + * + * @param string $filename + * @return mixed + */ + #[\ReturnTypeWillChange] + public function __invoke($filename) + { + return $this->offsetGet($filename); + } + + /** + * Set file modification timestamps (query params) for all the media files. + * + * @param string|int|null $timestamp + * @return $this + */ + public function setTimestamps($timestamp = null) + { + foreach ($this->items as $instance) { + $instance->setTimestamp($timestamp); + } + + return $this; + } + + /** + * Get a list of all media. + * + * @return MediaObjectInterface[] + */ + public function all() + { + $this->items = $this->orderMedia($this->items); + + return $this->items; + } + + /** + * Get a list of all image media. + * + * @return MediaObjectInterface[] + */ + public function images() + { + $this->images = $this->orderMedia($this->images); + + return $this->images; + } + + /** + * Get a list of all video media. + * + * @return MediaObjectInterface[] + */ + public function videos() + { + $this->videos = $this->orderMedia($this->videos); + + return $this->videos; + } + + /** + * Get a list of all audio media. + * + * @return MediaObjectInterface[] + */ + public function audios() + { + $this->audios = $this->orderMedia($this->audios); + + return $this->audios; + } + + /** + * Get a list of all file media. + * + * @return MediaObjectInterface[] + */ + public function files() + { + $this->files = $this->orderMedia($this->files); + + return $this->files; + } + + /** + * @param string $name + * @param MediaObjectInterface|null $file + * @return void + */ + public function add($name, $file) + { + if (null === $file) { + return; + } + + $this->offsetSet($name, $file); + + switch ($file->type) { + case 'image': + $this->images[$name] = $file; + break; + case 'video': + $this->videos[$name] = $file; + break; + case 'audio': + $this->audios[$name] = $file; + break; + default: + $this->files[$name] = $file; + } + } + + /** + * @param string $name + * @return void + */ + public function hide($name) + { + $this->offsetUnset($name); + + unset($this->images[$name], $this->videos[$name], $this->audios[$name], $this->files[$name]); + } + + /** + * Create Medium from a file. + * + * @param string $file + * @param array $params + * @return Medium|null + */ + public function createFromFile($file, array $params = []) + { + return MediumFactory::fromFile($file, $params); + } + + /** + * Create Medium from array of parameters + * + * @param array $items + * @param Blueprint|null $blueprint + * @return Medium|null + */ + public function createFromArray(array $items = [], Blueprint $blueprint = null) + { + return MediumFactory::fromArray($items, $blueprint); + } + + /** + * @param MediaObjectInterface $mediaObject + * @return ImageFile + */ + public function getImageFileObject(MediaObjectInterface $mediaObject): ImageFile + { + return ImageFile::open($mediaObject->get('filepath')); + } + + /** + * Order the media based on the page's media_order + * + * @param array $media + * @return array + */ + protected function orderMedia($media) + { + if (null === $this->media_order) { + $path = $this->getPath(); + if (null !== $path) { + /** @var Pages $pages */ + $pages = Grav::instance()['pages']; + $page = $pages->get($path); + if ($page && isset($page->header()->media_order)) { + $this->media_order = array_map('trim', explode(',', $page->header()->media_order)); + } + } + } + + if (!empty($this->media_order) && is_array($this->media_order)) { + $media = Utils::sortArrayByArray($media, $this->media_order); + } else { + ksort($media, SORT_NATURAL | SORT_FLAG_CASE); + } + + return $media; + } + + protected function fileExists(string $filename, string $destination): bool + { + return file_exists("{$destination}/{$filename}"); + } + + /** + * Get filename, extension and meta part. + * + * @param string $filename + * @return array + */ + protected function getFileParts($filename) + { + if (preg_match('/(.*)@(\d+)x\.(.*)$/', $filename, $matches)) { + $name = $matches[1]; + $extension = $matches[3]; + $extra = (int) $matches[2]; + $type = 'alternative'; + + if ($extra === 1) { + $type = 'base'; + $extra = null; + } + } else { + $fileParts = explode('.', $filename); + + $name = array_shift($fileParts); + $extension = null; + $extra = null; + $type = 'base'; + + while (($part = array_shift($fileParts)) !== null) { + if ($part !== 'meta' && $part !== 'thumb') { + if (null !== $extension) { + $name .= '.' . $extension; + } + $extension = $part; + } else { + $type = $part; + $extra = '.' . $part . '.' . implode('.', $fileParts); + break; + } + } + } + + return [$name, $extension, $type, $extra]; + } + + protected function getGrav(): Grav + { + return Grav::instance(); + } + + protected function getConfig(): Config + { + return $this->getGrav()['config']; + } + + protected function getLanguage(): Language + { + return $this->getGrav()['language']; + } + + protected function clearCache(): void + { + /** @var UniformResourceLocator $locator */ + $locator = $this->getGrav()['locator']; + $locator->clearCache(); + } +} diff --git a/system/src/Grav/Common/Page/Medium/AudioMedium.php b/system/src/Grav/Common/Page/Medium/AudioMedium.php new file mode 100644 index 0000000..f565512 --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/AudioMedium.php @@ -0,0 +1,36 @@ +resetPlayer(); + + return $this; + } +} diff --git a/system/src/Grav/Common/Page/Medium/GlobalMedia.php b/system/src/Grav/Common/Page/Medium/GlobalMedia.php new file mode 100644 index 0000000..3f75ad1 --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/GlobalMedia.php @@ -0,0 +1,150 @@ +resolveStream($offset)); + } + + /** + * @param string $offset + * @return MediaObjectInterface|null + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return parent::offsetGet($offset) ?: $this->addMedium($offset); + } + + /** + * @param string $filename + * @return string|null + */ + protected function resolveStream($filename) + { + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + if (!$locator->isStream($filename)) { + return null; + } + + return $locator->findResource($filename) ?: null; + } + + /** + * @param string $stream + * @return MediaObjectInterface|null + */ + protected function addMedium($stream) + { + $filename = $this->resolveStream($stream); + if (!$filename) { + return null; + } + + $path = dirname($filename); + [$basename, $ext,, $extra] = $this->getFileParts(Utils::basename($filename)); + $medium = MediumFactory::fromFile($filename); + + if (null === $medium) { + return null; + } + + $medium->set('size', filesize($filename)); + $scale = (int) ($extra ?: 1); + + if ($scale !== 1) { + $altMedium = $medium; + + // Create scaled down regular sized image. + $medium = MediumFactory::scaledFromMedium($altMedium, $scale, 1)['file']; + + if (empty($medium)) { + return null; + } + + // Add original sized image as alternative. + $medium->addAlternative($scale, $altMedium['file']); + + // Locate or generate smaller retina images. + for ($i = $scale-1; $i > 1; $i--) { + $altFilename = "{$path}/{$basename}@{$i}x.{$ext}"; + + if (file_exists($altFilename)) { + $scaled = MediumFactory::fromFile($altFilename); + } else { + $scaled = MediumFactory::scaledFromMedium($altMedium, $scale, $i)['file']; + } + + if ($scaled) { + $medium->addAlternative($i, $scaled); + } + } + } + + $meta = "{$path}/{$basename}.{$ext}.yaml"; + if (file_exists($meta)) { + $medium->addMetaFile($meta); + } + $meta = "{$path}/{$basename}.{$ext}.meta.yaml"; + if (file_exists($meta)) { + $medium->addMetaFile($meta); + } + + $thumb = "{$path}/{$basename}.thumb.{$ext}"; + if (file_exists($thumb)) { + $medium->set('thumbnails.page', $thumb); + } + + $this->add($stream, $medium); + + return $medium; + } +} diff --git a/system/src/Grav/Common/Page/Medium/ImageFile.php b/system/src/Grav/Common/Page/Medium/ImageFile.php new file mode 100644 index 0000000..e5cf6b0 --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/ImageFile.php @@ -0,0 +1,235 @@ +get('system.images.adapter', 'gd'); + try { + $this->setAdapter($adapter); + } catch (Exception $e) { + $grav['log']->error( + 'Image adapter "' . $adapter . '" is not available. Falling back to GD adapter.' + ); + } + } + + /** + * Destruct also image object. + */ + #[\ReturnTypeWillChange] + public function __destruct() + { + $adapter = $this->adapter; + if ($adapter) { + $adapter->deinit(); + } + } + + /** + * Clear previously applied operations + * + * @return void + */ + public function clearOperations() + { + $this->operations = []; + } + + /** + * This is the same as the Gregwar Image class except this one fires a Grav Event on creation of new cached file + * + * @param string $type the image type + * @param int $quality the quality (for JPEG) + * @param bool $actual + * @param array $extras + * @return string + */ + public function cacheFile($type = 'jpg', $quality = 80, $actual = false, $extras = []) + { + if ($type === 'guess') { + $type = $this->guessType(); + } + + if (!$this->forceCache && !count($this->operations) && $type === $this->guessType()) { + return $this->getFilename($this->getFilePath()); + } + + // Computes the hash + $this->hash = $this->getHash($type, $quality, $extras); + + /** @var Config $config */ + $config = Grav::instance()['config']; + + // Seo friendly image names + $seofriendly = $config->get('system.images.seofriendly', false); + + if ($seofriendly) { + $mini_hash = substr($this->hash, 0, 4) . substr($this->hash, -4); + $cacheFile = "{$this->prettyName}-{$mini_hash}"; + } else { + $cacheFile = "{$this->hash}-{$this->prettyName}"; + } + + $cacheFile .= '.' . $type; + + // If the files does not exists, save it + $image = $this; + + // Target file should be younger than all the current image + // dependencies + $conditions = array( + 'younger-than' => $this->getDependencies() + ); + + // The generating function + $generate = function ($target) use ($image, $type, $quality) { + $result = $image->save($target, $type, $quality); + + if ($result !== $target) { + throw new GenerationError($result); + } + + Grav::instance()->fireEvent('onImageMediumSaved', new Event(['image' => $target])); + }; + + // Asking the cache for the cacheFile + try { + $perms = $config->get('system.images.cache_perms', '0755'); + $perms = octdec($perms); + $file = $this->getCacheSystem()->setDirectoryMode($perms)->getOrCreateFile($cacheFile, $conditions, $generate, $actual); + } catch (GenerationError $e) { + $file = $e->getNewFile(); + } + + // Nulling the resource + $adapter = $this->getAdapter(); + $adapter->setSource(new Source\File($file)); + $adapter->deinit(); + + if ($actual) { + return $file; + } + + return $this->getFilename($file); + } + + /** + * Gets the hash. + * + * @param string $type + * @param int $quality + * @param array $extras + * @return string + */ + public function getHash($type = 'guess', $quality = 80, $extras = []) + { + if (null === $this->hash) { + $this->generateHash($type, $quality, $extras); + } + + return $this->hash; + } + + /** + * Generates the hash. + * + * @param string $type + * @param int $quality + * @param array $extras + */ + public function generateHash($type = 'guess', $quality = 80, $extras = []) + { + $inputInfos = $this->source->getInfos(); + + $data = [ + $inputInfos, + $this->serializeOperations(), + $type, + $quality, + $extras + ]; + + $this->hash = sha1(serialize($data)); + } + + /** + * Read exif rotation from file and apply it. + */ + public function fixOrientation() + { + if (!extension_loaded('exif')) { + throw new RuntimeException('You need to EXIF PHP Extension to use this function'); + } + + if (!file_exists($this->source->getInfos()) || !in_array(exif_imagetype($this->source->getInfos()), [IMAGETYPE_JPEG, IMAGETYPE_TIFF_II, IMAGETYPE_TIFF_MM], true)) { + return $this; + } + + // resolve any streams + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + $filepath = $this->source->getInfos(); + if ($locator->isStream($filepath)) { + $filepath = $locator->findResource($this->source->getInfos(), true, true); + } + + // Make sure file exists + if (!file_exists($filepath)) { + return $this; + } + + try { + $exif = @exif_read_data($filepath); + } catch (Exception $e) { + Grav::instance()['log']->error($filepath . ' - ' . $e->getMessage()); + return $this; + } + + if ($exif === false || !array_key_exists('Orientation', $exif)) { + return $this; + } + + return $this->applyExifOrientation($exif['Orientation']); + } +} diff --git a/system/src/Grav/Common/Page/Medium/ImageMedium.php b/system/src/Grav/Common/Page/Medium/ImageMedium.php new file mode 100644 index 0000000..7122b5f --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/ImageMedium.php @@ -0,0 +1,499 @@ +getGrav()['config']; + + $this->thumbnailTypes = ['page', 'media', 'default']; + $this->default_quality = $config->get('system.images.default_image_quality', 85); + $this->def('debug', $config->get('system.images.debug')); + + $path = $this->get('filepath'); + if (!$path || !file_exists($path) || !filesize($path)) { + return; + } + + $this->set('thumbnails.media', $path); + + if (!($this->offsetExists('width') && $this->offsetExists('height') && $this->offsetExists('mime'))) { + $image_info = getimagesize($path); + if ($image_info) { + $this->def('width', (int) $image_info[0]); + $this->def('height', (int) $image_info[1]); + $this->def('mime', $image_info['mime']); + } + } + + $this->reset(); + + if ($config->get('system.images.cache_all', false)) { + $this->cache(); + } + } + + /** + * @return array + */ + public function getMeta(): array + { + return [ + 'width' => $this->width, + 'height' => $this->height, + ] + parent::getMeta(); + } + + /** + * Also unset the image on destruct. + */ + #[\ReturnTypeWillChange] + public function __destruct() + { + unset($this->image); + } + + /** + * Also clone image. + */ + #[\ReturnTypeWillChange] + public function __clone() + { + if ($this->image) { + $this->image = clone $this->image; + } + + parent::__clone(); + } + + /** + * Reset image. + * + * @return $this + */ + public function reset() + { + parent::reset(); + + if ($this->image) { + $this->image(); + $this->medium_querystring = []; + $this->filter(); + $this->clearAlternatives(); + } + + $this->format = 'guess'; + $this->quality = $this->default_quality; + + $this->debug_watermarked = false; + + $config = $this->getGrav()['config']; + // Set CLS configuration + $this->auto_sizes = $config->get('system.images.cls.auto_sizes', false); + $this->aspect_ratio = $config->get('system.images.cls.aspect_ratio', false); + $this->retina_scale = $config->get('system.images.cls.retina_scale', 1); + + return $this; + } + + /** + * Add meta file for the medium. + * + * @param string $filepath + * @return $this + */ + public function addMetaFile($filepath) + { + parent::addMetaFile($filepath); + + // Apply filters in meta file + $this->reset(); + + return $this; + } + + /** + * Return PATH to image. + * + * @param bool $reset + * @return string path to image + */ + public function path($reset = true) + { + $output = $this->saveImage(); + + if ($reset) { + $this->reset(); + } + + return $output; + } + + /** + * Return URL to image. + * + * @param bool $reset + * @return string + */ + public function url($reset = true) + { + $grav = $this->getGrav(); + + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + $image_path = (string)($locator->findResource('cache://images', true) ?: $locator->findResource('cache://images', true, true)); + $saved_image_path = $this->saved_image_path = $this->saveImage(); + + $output = preg_replace('|^' . preg_quote(GRAV_ROOT, '|') . '|', '', $saved_image_path) ?: $saved_image_path; + + if ($locator->isStream($output)) { + $output = (string)($locator->findResource($output, false) ?: $locator->findResource($output, false, true)); + } + + if (Utils::startsWith($output, $image_path)) { + $image_dir = $locator->findResource('cache://images', false); + $output = '/' . $image_dir . preg_replace('|^' . preg_quote($image_path, '|') . '|', '', $output); + } + + if ($reset) { + $this->reset(); + } + + return trim($grav['base_url'] . '/' . $this->urlQuerystring($output), '\\'); + } + + /** + * Return srcset string for this Medium and its alternatives. + * + * @param bool $reset + * @return string + */ + public function srcset($reset = true) + { + if (empty($this->alternatives)) { + if ($reset) { + $this->reset(); + } + + return ''; + } + + $srcset = []; + foreach ($this->alternatives as $ratio => $medium) { + $srcset[] = $medium->url($reset) . ' ' . $medium->get('width') . 'w'; + } + $srcset[] = str_replace(' ', '%20', $this->url($reset)) . ' ' . $this->get('width') . 'w'; + + return implode(', ', $srcset); + } + + /** + * Parsedown element for source display mode + * + * @param array $attributes + * @param bool $reset + * @return array + */ + public function sourceParsedownElement(array $attributes, $reset = true) + { + empty($attributes['src']) && $attributes['src'] = $this->url(false); + + $srcset = $this->srcset($reset); + if ($srcset) { + empty($attributes['srcset']) && $attributes['srcset'] = $srcset; + $attributes['sizes'] = $this->sizes(); + } + + if ($this->saved_image_path && $this->auto_sizes) { + if (!array_key_exists('height', $this->attributes) && !array_key_exists('width', $this->attributes)) { + $info = getimagesize($this->saved_image_path); + $width = (int)$info[0]; + $height = (int)$info[1]; + + $scaling_factor = $this->retina_scale > 0 ? $this->retina_scale : 1; + $attributes['width'] = (int)($width / $scaling_factor); + $attributes['height'] = (int)($height / $scaling_factor); + + if ($this->aspect_ratio) { + $style = ($attributes['style'] ?? ' ') . "--aspect-ratio: $width/$height;"; + $attributes['style'] = trim($style); + } + } + } + + return ['name' => 'img', 'attributes' => $attributes]; + } + + /** + * Turn the current Medium into a Link + * + * @param bool $reset + * @param array $attributes + * @return MediaLinkInterface + */ + public function link($reset = true, array $attributes = []) + { + $attributes['href'] = $this->url(false); + $srcset = $this->srcset(false); + if ($srcset) { + $attributes['data-srcset'] = $srcset; + } + + return parent::link($reset, $attributes); + } + + /** + * Turn the current Medium into a Link with lightbox enabled + * + * @param int $width + * @param int $height + * @param bool $reset + * @return MediaLinkInterface + */ + public function lightbox($width = null, $height = null, $reset = true) + { + if ($this->mode !== 'source') { + $this->display('source'); + } + + if ($width && $height) { + $this->__call('cropResize', [(int) $width, (int) $height]); + } + + return parent::lightbox($width, $height, $reset); + } + + /** + * @param string $enabled + * @return $this + */ + public function autoSizes($enabled = 'true') + { + $this->auto_sizes = $enabled === 'true' ?: false; + + return $this; + } + + /** + * @param string $enabled + * @return $this + */ + public function aspectRatio($enabled = 'true') + { + $this->aspect_ratio = $enabled === 'true' ?: false; + + return $this; + } + + /** + * @param int $scale + * @return $this + */ + public function retinaScale($scale = 1) + { + $this->retina_scale = (int)$scale; + + return $this; + } + + /** + * @param string|null $image + * @param string|null $position + * @param int|float|null $scale + * @return $this + */ + public function watermark($image = null, $position = null, $scale = null) + { + $grav = $this->getGrav(); + + $locator = $grav['locator']; + $config = $grav['config']; + + $args = func_get_args(); + + $file = $args[0] ?? '1'; // using '1' because of markdown. doing ![](image.jpg?watermark) returns $args[0]='1'; + $file = $file === '1' ? $config->get('system.images.watermark.image') : $args[0]; + + $watermark = $locator->findResource($file); + $watermark = ImageFile::open($watermark); + + // Scaling operations + $scale = ($scale ?? $config->get('system.images.watermark.scale', 100)) / 100; + $wwidth = (int) ($this->get('width') * $scale); + $wheight = (int) ($this->get('height') * $scale); + $watermark->resize($wwidth, $wheight); + + // Position operations + $position = !empty($args[1]) ? explode('-', $args[1]) : ['center', 'center']; // todo change to config + $positionY = $position[0] ?? $config->get('system.images.watermark.position_y', 'center'); + $positionX = $position[1] ?? $config->get('system.images.watermark.position_x', 'center'); + + switch ($positionY) + { + case 'top': + $positionY = 0; + break; + + case 'bottom': + $positionY = (int)$this->get('height')-$wheight; + break; + + case 'center': + $positionY = ((int)$this->get('height')/2) - ($wheight/2); + break; + } + + switch ($positionX) + { + case 'left': + $positionX = 0; + break; + + case 'right': + $positionX = (int) ($this->get('width')-$wwidth); + break; + + case 'center': + $positionX = (int) (($this->get('width')/2) - ($wwidth/2)); + break; + } + + $this->__call('merge', [$watermark,$positionX, $positionY]); + + return $this; + } + + /** + * Handle this commonly used variant + * + * @return $this + */ + public function cropZoom() + { + $this->__call('zoomCrop', func_get_args()); + + return $this; + } + + /** + * Add a frame to image + * + * @return $this + */ + public function addFrame(int $border = 10, string $color = '0x000000') + { + if($border > 0 && preg_match('/^0x[a-f0-9]{6}$/i', $color)) { // $border must be an integer and bigger than 0; $color must be formatted as an HEX value (0x??????). + $image = ImageFile::open($this->path()); + } + else { + return $this; + } + + $dst_width = (int) ($image->width()+2*$border); + $dst_height = (int) ($image->height()+2*$border); + + $frame = ImageFile::create($dst_width, $dst_height); + + $frame->__call('fill', [$color]); + + $this->image = $frame; + + $this->__call('merge', [$image, $border, $border]); + + $this->saveImage(); + + return $this; + + } + + /** + * Forward the call to the image processing method. + * + * @param string $method + * @param mixed $args + * @return $this|mixed + */ + #[\ReturnTypeWillChange] + public function __call($method, $args) + { + if (!in_array($method, static::$magic_actions, true)) { + return parent::__call($method, $args); + } + + // Always initialize image. + if (!$this->image) { + $this->image(); + } + + try { + $this->image->{$method}(...$args); + + /** @var ImageMediaInterface $medium */ + foreach ($this->alternatives as $medium) { + $args_copy = $args; + + // regular image: resize 400x400 -> 200x200 + // --> @2x: resize 800x800->400x400 + if (isset(static::$magic_resize_actions[$method])) { + foreach (static::$magic_resize_actions[$method] as $param) { + if (isset($args_copy[$param])) { + $args_copy[$param] *= $medium->get('ratio'); + } + } + } + + // Do the same call for alternative media. + $medium->__call($method, $args_copy); + } + } catch (BadFunctionCallException $e) { + } + + return $this; + } +} diff --git a/system/src/Grav/Common/Page/Medium/Link.php b/system/src/Grav/Common/Page/Medium/Link.php new file mode 100644 index 0000000..cb83ace --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/Link.php @@ -0,0 +1,102 @@ +attributes = $attributes; + + $source = $medium->reset()->thumbnail('auto')->display('thumbnail'); + if (!$source instanceof MediaObjectInterface) { + throw new RuntimeException('Media has no thumbnail set'); + } + + $source->set('linked', true); + + $this->source = $source; + } + + /** + * Get an element (is array) that can be rendered by the Parsedown engine + * + * @param string|null $title + * @param string|null $alt + * @param string|null $class + * @param string|null $id + * @param bool $reset + * @return array + */ + public function parsedownElement($title = null, $alt = null, $class = null, $id = null, $reset = true) + { + $innerElement = $this->source->parsedownElement($title, $alt, $class, $id, $reset); + + return [ + 'name' => 'a', + 'attributes' => $this->attributes, + 'handler' => is_array($innerElement) ? 'element' : 'line', + 'text' => $innerElement + ]; + } + + /** + * Forward the call to the source element + * + * @param string $method + * @param mixed $args + * @return mixed + */ + #[\ReturnTypeWillChange] + public function __call($method, $args) + { + $object = $this->source; + $callable = [$object, $method]; + if (!is_callable($callable)) { + throw new BadMethodCallException(get_class($object) . '::' . $method . '() not found.'); + } + + $object = call_user_func_array($callable, $args); + if (!$object instanceof MediaLinkInterface) { + // Don't start nesting links, if user has multiple link calls in his + // actions, we will drop the previous links. + return $this; + } + + $this->source = $object; + + return $object; + } +} diff --git a/system/src/Grav/Common/Page/Medium/Medium.php b/system/src/Grav/Common/Page/Medium/Medium.php new file mode 100644 index 0000000..fe53c80 --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/Medium.php @@ -0,0 +1,140 @@ +get('system.media.enable_media_timestamp', true)) { + $this->timestamp = Grav::instance()['cache']->getKey(); + } + + $this->def('mime', 'application/octet-stream'); + + if (!$this->offsetExists('size')) { + $path = $this->get('filepath'); + $this->def('size', filesize($path)); + } + + $this->reset(); + } + + /** + * Clone medium. + */ + #[\ReturnTypeWillChange] + public function __clone() + { + // Allows future compatibility as parent::__clone() works. + } + + /** + * Add meta file for the medium. + * + * @param string $filepath + */ + public function addMetaFile($filepath) + { + $this->metadata = (array)CompiledYamlFile::instance($filepath)->content(); + $this->merge($this->metadata); + } + + /** + * @return array + */ + public function getMeta(): array + { + return [ + 'mime' => $this->mime, + 'size' => $this->size, + 'modified' => $this->modified, + ]; + } + + /** + * Return string representation of the object (html). + * + * @return string + */ + #[\ReturnTypeWillChange] + public function __toString() + { + return $this->html(); + } + + /** + * @param string $thumb + * @return Medium|null + */ + protected function createThumbnail($thumb) + { + return MediumFactory::fromFile($thumb, ['type' => 'thumbnail']); + } + + /** + * @param array $attributes + * @return MediaLinkInterface + */ + protected function createLink(array $attributes) + { + return new Link($attributes, $this); + } + + /** + * @return Grav + */ + protected function getGrav(): Grav + { + return Grav::instance(); + } + + /** + * @return array + */ + protected function getItems(): array + { + return $this->items; + } +} diff --git a/system/src/Grav/Common/Page/Medium/MediumFactory.php b/system/src/Grav/Common/Page/Medium/MediumFactory.php new file mode 100644 index 0000000..838946b --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/MediumFactory.php @@ -0,0 +1,220 @@ +get('media.types.' . strtolower($ext)) : null; + if (!is_array($media_params)) { + return null; + } + + // Remove empty 'image' attribute + if (isset($media_params['image']) && empty($media_params['image'])) { + unset($media_params['image']); + } + + $params += $media_params; + + // Add default settings for undefined variables. + $params += (array)$config->get('media.types.defaults'); + $params += [ + 'type' => 'file', + 'thumb' => 'media/thumb.png', + 'mime' => 'application/octet-stream', + 'filepath' => $file, + 'filename' => $filename, + 'basename' => $basename, + 'extension' => $ext, + 'path' => $path, + 'modified' => filemtime($file), + 'thumbnails' => [] + ]; + + $locator = Grav::instance()['locator']; + + $file = $locator->findResource("image://{$params['thumb']}"); + if ($file) { + $params['thumbnails']['default'] = $file; + } + + return static::fromArray($params); + } + + /** + * Create Medium from an uploaded file + * + * @param UploadedFileInterface $uploadedFile + * @param array $params + * @return Medium|null + */ + public static function fromUploadedFile(UploadedFileInterface $uploadedFile, array $params = []) + { + // For now support only FormFlashFiles, which exist over multiple requests. Also ignore errored and moved media. + if (!$uploadedFile instanceof FormFlashFile || $uploadedFile->getError() !== \UPLOAD_ERR_OK || $uploadedFile->isMoved()) { + return null; + } + + $clientName = $uploadedFile->getClientFilename(); + if (!$clientName) { + return null; + } + + $parts = Utils::pathinfo($clientName); + $filename = $parts['basename']; + $ext = $parts['extension'] ?? ''; + $basename = $parts['filename']; + $file = $uploadedFile->getTmpFile(); + $path = $file ? dirname($file) : ''; + + $config = Grav::instance()['config']; + + $media_params = $ext ? $config->get('media.types.' . strtolower($ext)) : null; + if (!is_array($media_params)) { + return null; + } + + $params += $media_params; + + // Add default settings for undefined variables. + $params += (array)$config->get('media.types.defaults'); + $params += [ + 'type' => 'file', + 'thumb' => 'media/thumb.png', + 'mime' => 'application/octet-stream', + 'filepath' => $file, + 'filename' => $filename, + 'basename' => $basename, + 'extension' => $ext, + 'path' => $path, + 'modified' => $file ? filemtime($file) : 0, + 'thumbnails' => [] + ]; + + $locator = Grav::instance()['locator']; + + $file = $locator->findResource("image://{$params['thumb']}"); + if ($file) { + $params['thumbnails']['default'] = $file; + } + + return static::fromArray($params); + } + + /** + * Create Medium from array of parameters + * + * @param array $items + * @param Blueprint|null $blueprint + * @return Medium + */ + public static function fromArray(array $items = [], Blueprint $blueprint = null) + { + $type = $items['type'] ?? null; + + switch ($type) { + case 'image': + return new ImageMedium($items, $blueprint); + case 'thumbnail': + return new ThumbnailImageMedium($items, $blueprint); + case 'vector': + return new VectorImageMedium($items, $blueprint); + case 'animated': + return new StaticImageMedium($items, $blueprint); + case 'video': + return new VideoMedium($items, $blueprint); + case 'audio': + return new AudioMedium($items, $blueprint); + default: + return new Medium($items, $blueprint); + } + } + + /** + * Create a new ImageMedium by scaling another ImageMedium object. + * + * @param ImageMediaInterface|MediaObjectInterface $medium + * @param int $from + * @param int $to + * @return ImageMediaInterface|MediaObjectInterface|array + */ + public static function scaledFromMedium($medium, $from, $to) + { + if (!$medium instanceof ImageMedium) { + return $medium; + } + + if ($to > $from) { + return $medium; + } + + $ratio = $to / $from; + $width = $medium->get('width') * $ratio; + $height = $medium->get('height') * $ratio; + + $prev_basename = $medium->get('basename'); + $basename = str_replace('@' . $from . 'x', $to !== 1 ? '@' . $to . 'x' : '', $prev_basename); + + $debug = $medium->get('debug'); + $medium->set('debug', false); + $medium->setImagePrettyName($basename); + + $file = $medium->resize($width, $height)->path(); + + $medium->set('debug', $debug); + $medium->setImagePrettyName($prev_basename); + + $size = filesize($file); + + $medium = self::fromFile($file); + if ($medium) { + $medium->set('basename', $basename); + $medium->set('filename', $basename . '.' . $medium->extension); + $medium->set('size', $size); + } + + return ['file' => $medium, 'size' => $size]; + } +} diff --git a/system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php b/system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php new file mode 100644 index 0000000..4b6e24c --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php @@ -0,0 +1,44 @@ +parsedownElement($title, $alt, $class, $id, $reset); + + if (!$this->parsedown) { + $this->parsedown = new Parsedown(new Excerpts()); + } + + return $this->parsedown->elementToHtml($element); + } +} diff --git a/system/src/Grav/Common/Page/Medium/RenderableInterface.php b/system/src/Grav/Common/Page/Medium/RenderableInterface.php new file mode 100644 index 0000000..6a71058 --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/RenderableInterface.php @@ -0,0 +1,41 @@ +url($reset); + } + + return ['name' => 'img', 'attributes' => $attributes]; + } + + /** + * @return $this + */ + public function higherQualityAlternative() + { + return $this; + } +} diff --git a/system/src/Grav/Common/Page/Medium/StaticResizeTrait.php b/system/src/Grav/Common/Page/Medium/StaticResizeTrait.php new file mode 100644 index 0000000..61ff6a1 --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/StaticResizeTrait.php @@ -0,0 +1,24 @@ +get('width'); + $height = $this->get('height'); + if ($width && $height) { + return; + } + + // Make sure that getting image size is supported. + if ($this->mime !== 'image/svg+xml' || !\extension_loaded('simplexml')) { + return; + } + + // Make sure that the image exists. + $path = $this->get('filepath'); + if (!$path || !file_exists($path) || !filesize($path)) { + return; + } + + $xml = simplexml_load_string(file_get_contents($path)); + $attr = $xml ? $xml->attributes() : null; + if (!$attr instanceof \SimpleXMLElement) { + return; + } + + // Get the size from svg image. + if ($attr->width && $attr->height) { + $width = (string)$attr->width; + $height = (string)$attr->height; + } elseif ($attr->viewBox && \count($size = explode(' ', (string)$attr->viewBox)) === 4) { + [,$width,$height,] = $size; + } + + if ($width && $height) { + $this->def('width', (int)$width); + $this->def('height', (int)$height); + } + } +} diff --git a/system/src/Grav/Common/Page/Medium/VideoMedium.php b/system/src/Grav/Common/Page/Medium/VideoMedium.php new file mode 100644 index 0000000..f299eee --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/VideoMedium.php @@ -0,0 +1,36 @@ +resetPlayer(); + + return $this; + } +} diff --git a/system/src/Grav/Common/Page/Page.php b/system/src/Grav/Common/Page/Page.php new file mode 100644 index 0000000..7cf9bf3 --- /dev/null +++ b/system/src/Grav/Common/Page/Page.php @@ -0,0 +1,2935 @@ +taxonomy = []; + $this->process = $config->get('system.pages.process'); + $this->published = true; + } + + /** + * Initializes the page instance variables based on a file + * + * @param SplFileInfo $file The file information for the .md file that the page represents + * @param string|null $extension + * @return $this + */ + public function init(SplFileInfo $file, $extension = null) + { + $config = Grav::instance()['config']; + + $this->initialized = true; + + // some extension logic + if (empty($extension)) { + $this->extension('.' . $file->getExtension()); + } else { + $this->extension($extension); + } + + // extract page language from page extension + $language = trim(Utils::basename($this->extension(), 'md'), '.') ?: null; + $this->language($language); + + $this->hide_home_route = $config->get('system.home.hide_in_urls', false); + $this->home_route = $this->adjustRouteCase($config->get('system.home.alias')); + $this->filePath($file->getPathname()); + $this->modified($file->getMTime()); + $this->id($this->modified() . md5($this->filePath())); + $this->routable(true); + $this->header(); + $this->date(); + $this->metadata(); + $this->url(); + $this->visible(); + $this->modularTwig(strpos($this->slug(), '_') === 0); + $this->setPublishState(); + $this->published(); + $this->urlExtension(); + + return $this; + } + + #[\ReturnTypeWillChange] + public function __clone() + { + $this->initialized = false; + $this->header = $this->header ? clone $this->header : null; + } + + /** + * @return void + */ + public function initialize(): void + { + if (!$this->initialized) { + $this->initialized = true; + $this->route = null; + $this->raw_route = null; + $this->_forms = null; + } + } + + /** + * @return void + */ + protected function processFrontmatter() + { + // Quick check for twig output tags in frontmatter if enabled + $process_fields = (array)$this->header(); + if (Utils::contains(json_encode(array_values($process_fields)), '{{')) { + $ignored_fields = []; + foreach ((array)Grav::instance()['config']->get('system.pages.frontmatter.ignore_fields') as $field) { + if (isset($process_fields[$field])) { + $ignored_fields[$field] = $process_fields[$field]; + unset($process_fields[$field]); + } + } + $text_header = Grav::instance()['twig']->processString(json_encode($process_fields, JSON_UNESCAPED_UNICODE), ['page' => $this]); + $this->header((object)(json_decode($text_header, true) + $ignored_fields)); + } + } + + /** + * Return an array with the routes of other translated languages + * + * @param bool $onlyPublished only return published translations + * @return array the page translated languages + */ + public function translatedLanguages($onlyPublished = false) + { + $grav = Grav::instance(); + + /** @var Language $language */ + $language = $grav['language']; + + $languages = $language->getLanguages(); + $defaultCode = $language->getDefault(); + + $name = substr($this->name, 0, -strlen($this->extension())); + $translatedLanguages = []; + + foreach ($languages as $languageCode) { + $languageExtension = ".{$languageCode}.md"; + $path = $this->path . DS . $this->folder . DS . $name . $languageExtension; + $exists = file_exists($path); + + // Default language may be saved without language file location. + if (!$exists && $languageCode === $defaultCode) { + $languageExtension = '.md'; + $path = $this->path . DS . $this->folder . DS . $name . $languageExtension; + $exists = file_exists($path); + } + + if ($exists) { + $aPage = new Page(); + $aPage->init(new SplFileInfo($path), $languageExtension); + $aPage->route($this->route()); + $aPage->rawRoute($this->rawRoute()); + $route = $aPage->header()->routes['default'] ?? $aPage->rawRoute(); + if (!$route) { + $route = $aPage->route(); + } + + if ($onlyPublished && !$aPage->published()) { + continue; + } + + $translatedLanguages[$languageCode] = $route; + } + } + + return $translatedLanguages; + } + + /** + * Return an array listing untranslated languages available + * + * @param bool $includeUnpublished also list unpublished translations + * @return array the page untranslated languages + */ + public function untranslatedLanguages($includeUnpublished = false) + { + $grav = Grav::instance(); + + /** @var Language $language */ + $language = $grav['language']; + + $languages = $language->getLanguages(); + $translated = array_keys($this->translatedLanguages(!$includeUnpublished)); + + return array_values(array_diff($languages, $translated)); + } + + /** + * Gets and Sets the raw data + * + * @param string|null $var Raw content string + * @return string Raw content string + */ + public function raw($var = null) + { + $file = $this->file(); + + if ($var) { + // First update file object. + if ($file) { + $file->raw($var); + } + + // Reset header and content. + $this->modified = time(); + $this->id($this->modified() . md5($this->filePath())); + $this->header = null; + $this->content = null; + $this->summary = null; + } + + return $file ? $file->raw() : ''; + } + + /** + * Gets and Sets the page frontmatter + * + * @param string|null $var + * + * @return string + */ + public function frontmatter($var = null) + { + if ($var) { + $this->frontmatter = (string)$var; + + // Update also file object. + $file = $this->file(); + if ($file) { + $file->frontmatter((string)$var); + } + + // Force content re-processing. + $this->id(time() . md5($this->filePath())); + } + if (!$this->frontmatter) { + $this->header(); + } + + return $this->frontmatter; + } + + /** + * Gets and Sets the header based on the YAML configuration at the top of the .md file + * + * @param object|array|null $var a YAML object representing the configuration for the file + * @return \stdClass 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) { + try { + $this->raw_content = $file->markdown(); + $this->frontmatter = $file->frontmatter(); + $this->header = (object)$file->header(); + + if (!Utils::isAdminPlugin()) { + // If there's a `frontmatter.yaml` file merge that in with the page header + // note page's own frontmatter has precedence and will overwrite any defaults + $frontmatter_filename = $this->path . '/' . $this->folder . '/frontmatter.yaml'; + if (file_exists($frontmatter_filename)) { + $frontmatter_file = CompiledYamlFile::instance($frontmatter_filename); + $frontmatter_data = $frontmatter_file->content(); + $this->header = (object)array_replace_recursive( + $frontmatter_data, + (array)$this->header + ); + $frontmatter_file->free(); + } + + // Process frontmatter with Twig if enabled + if (Grav::instance()['config']->get('system.pages.frontmatter.process_twig') === true) { + $this->processFrontmatter(); + } + } + } catch (Exception $e) { + $file->raw(Grav::instance()['language']->translate([ + 'GRAV.FRONTMATTER_ERROR_PAGE', + $this->slug(), + $file->filename(), + $e->getMessage(), + $file->raw() + ])); + $this->raw_content = $file->markdown(); + $this->frontmatter = $file->frontmatter(); + $this->header = (object)$file->header(); + } + $var = true; + } + } + + if ($var) { + if (isset($this->header->modified)) { + $this->modified($this->header->modified); + } + if (isset($this->header->slug)) { + $this->slug($this->header->slug); + } + if (isset($this->header->routes)) { + $this->routes = (array)$this->header->routes; + } + if (isset($this->header->title)) { + $this->title = trim($this->header->title); + } + if (isset($this->header->language)) { + $this->language = trim($this->header->language); + } + if (isset($this->header->template)) { + $this->template = trim($this->header->template); + } + if (isset($this->header->menu)) { + $this->menu = trim($this->header->menu); + } + if (isset($this->header->routable)) { + $this->routable = (bool)$this->header->routable; + } + if (isset($this->header->visible)) { + $this->visible = (bool)$this->header->visible; + } + if (isset($this->header->redirect)) { + $this->redirect = trim($this->header->redirect); + } + if (isset($this->header->external_url)) { + $this->external_url = trim($this->header->external_url); + } + 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->dateformat)) { + $this->dateformat($this->header->dateformat); + } + if (isset($this->header->date)) { + $this->date($this->header->date); + } + if (isset($this->header->markdown_extra)) { + $this->markdown_extra = (bool)$this->header->markdown_extra; + } + if (isset($this->header->taxonomy)) { + $this->taxonomy($this->header->taxonomy); + } + if (isset($this->header->max_count)) { + $this->max_count = (int)$this->header->max_count; + } + if (isset($this->header->process)) { + foreach ((array)$this->header->process as $process => $status) { + $this->process[$process] = (bool)$status; + } + } + if (isset($this->header->published)) { + $this->published = (bool)$this->header->published; + } + if (isset($this->header->publish_date)) { + $this->publishDate($this->header->publish_date); + } + if (isset($this->header->unpublish_date)) { + $this->unpublishDate($this->header->unpublish_date); + } + if (isset($this->header->expires)) { + $this->expires = (int)$this->header->expires; + } + if (isset($this->header->cache_control)) { + $this->cache_control = $this->header->cache_control; + } + if (isset($this->header->etag)) { + $this->etag = (bool)$this->header->etag; + } + if (isset($this->header->last_modified)) { + $this->last_modified = (bool)$this->header->last_modified; + } + if (isset($this->header->ssl)) { + $this->ssl = (bool)$this->header->ssl; + } + if (isset($this->header->template_format)) { + $this->template_format = $this->header->template_format; + } + if (isset($this->header->debugger)) { + $this->debugger = (bool)$this->header->debugger; + } + if (isset($this->header->append_url_extension)) { + $this->url_extension = $this->header->append_url_extension; + } + } + + return $this->header; + } + + /** + * Get page language + * + * @param string|null $var + * @return mixed + */ + public function language($var = null) + { + if ($var !== null) { + $this->language = $var; + } + + return $this->language; + } + + /** + * Modify a header value directly + * + * @param string $key + * @param mixed $value + */ + public function modifyHeader($key, $value) + { + $this->header->{$key} = $value; + } + + /** + * @return int + */ + public function httpResponseCode() + { + return (int)($this->header()->http_response_code ?? 200); + } + + /** + * @return array + */ + public function httpHeaders() + { + $headers = []; + + $grav = Grav::instance(); + $format = $this->templateFormat(); + $cache_control = $this->cacheControl(); + $expires = $this->expires(); + + // Set Content-Type header + $headers['Content-Type'] = Utils::getMimeByExtension($format, 'text/html'); + + // Calculate Expires Headers if set to > 0 + if ($expires > 0) { + $expires_date = gmdate('D, d M Y H:i:s', time() + $expires) . ' GMT'; + if (!$cache_control) { + $headers['Cache-Control'] = 'max-age=' . $expires; + } + $headers['Expires'] = $expires_date; + } + + // Set Cache-Control header + if ($cache_control) { + $headers['Cache-Control'] = strtolower($cache_control); + } + + // Set Last-Modified header + if ($this->lastModified()) { + $last_modified = $this->modified(); + foreach ($this->children()->modular() as $cpage) { + $modular_mtime = $cpage->modified(); + if ($modular_mtime > $last_modified) { + $last_modified = $modular_mtime; + } + } + + $last_modified_date = gmdate('D, d M Y H:i:s', $last_modified) . ' GMT'; + $headers['Last-Modified'] = $last_modified_date; + } + + // Ask Grav to calculate ETag from the final content. + if ($this->eTag()) { + $headers['ETag'] = '1'; + } + + // Set Vary: Accept-Encoding header + if ($grav['config']->get('system.pages.vary_accept_encoding', false)) { + $headers['Vary'] = 'Accept-Encoding'; + } + + + // Added new Headers event + $headers_obj = (object) $headers; + Grav::instance()->fireEvent('onPageHeaders', new Event(['headers' => $headers_obj])); + + return (array)$headers_obj; + } + + /** + * Get the summary. + * + * @param int|null $size Max summary size. + * @param bool $textOnly Only count text size. + * @return string + */ + public function summary($size = null, $textOnly = false) + { + $config = (array)Grav::instance()['config']->get('site.summary'); + if (isset($this->header->summary)) { + $config = array_merge($config, $this->header->summary); + } + + // Return summary based on settings in site config file + if (!$config['enabled']) { + return $this->content(); + } + + // Set up variables to process summary from page or from custom summary + if ($this->summary === null) { + $content = $textOnly ? strip_tags($this->content()) : $this->content(); + $summary_size = $this->summary_size; + } else { + $content = $textOnly ? strip_tags($this->summary) : $this->summary; + $summary_size = mb_strwidth($content, 'utf-8'); + } + + // Return calculated summary based on summary divider's position + $format = $config['format']; + // Return entire page content on wrong/ unknown format + if (!in_array($format, ['short', 'long'])) { + return $content; + } + if (($format === 'short') && isset($summary_size)) { + // Slice the string + if (mb_strwidth($content, 'utf8') > $summary_size) { + return mb_substr($content, 0, $summary_size); + } + + return $content; + } + + // Get summary size from site config's file + if ($size === null) { + $size = $config['size']; + } + + // If the size is zero, return the entire page content + if ($size === 0) { + return $content; + // Return calculated summary based on defaults + } + if (!is_numeric($size) || ($size < 0)) { + $size = 300; + } + + // Only return string but not html, wrap whatever html tag you want when using + if ($textOnly) { + if (mb_strwidth($content, 'utf-8') <= $size) { + return $content; + } + + return mb_strimwidth($content, 0, $size, '…', 'UTF-8'); + } + + $summary = Utils::truncateHtml($content, $size); + + return html_entity_decode($summary, ENT_COMPAT | ENT_HTML401, 'UTF-8'); + } + + /** + * Sets the summary of the page + * + * @param string $summary Summary + */ + public function setSummary($summary) + { + $this->summary = $summary; + } + + /** + * Gets and Sets the content based on content portion of the .md file + * + * @param string|null $var Content + * @return string Content + */ + public function content($var = null) + { + if ($var !== null) { + $this->raw_content = $var; + + // Update file object. + $file = $this->file(); + if ($file) { + $file->markdown($var); + } + + // Force re-processing. + $this->id(time() . md5($this->filePath())); + $this->content = null; + } + // If no content, process it + if ($this->content === null) { + // Get media + $this->media(); + + /** @var Config $config */ + $config = Grav::instance()['config']; + + // Load cached content + /** @var Cache $cache */ + $cache = Grav::instance()['cache']; + $cache_id = md5('page' . $this->getCacheKey()); + $content_obj = $cache->fetch($cache_id); + + if (is_array($content_obj)) { + $this->content = $content_obj['content']; + $this->content_meta = $content_obj['content_meta']; + } else { + $this->content = $content_obj; + } + + + $process_markdown = $this->shouldProcess('markdown'); + $process_twig = $this->shouldProcess('twig') || $this->modularTwig(); + + $cache_enable = $this->header->cache_enable ?? $config->get( + 'system.cache.enabled', + true + ); + $twig_first = $this->header->twig_first ?? $config->get( + 'system.pages.twig_first', + false + ); + + // never cache twig means it's always run after content + $never_cache_twig = $this->header->never_cache_twig ?? $config->get( + 'system.pages.never_cache_twig', + true + ); + + // if no cached-content run everything + if ($never_cache_twig) { + if ($this->content === false || $cache_enable === false) { + $this->content = $this->raw_content; + Grav::instance()->fireEvent('onPageContentRaw', new Event(['page' => $this])); + + if ($process_markdown) { + $this->processMarkdown(); + } + + // Content Processed but not cached yet + Grav::instance()->fireEvent('onPageContentProcessed', new Event(['page' => $this])); + + if ($cache_enable) { + $this->cachePageContent(); + } + } + + if ($process_twig) { + $this->processTwig(); + } + } else { + if ($this->content === false || $cache_enable === false) { + $this->content = $this->raw_content; + Grav::instance()->fireEvent('onPageContentRaw', new Event(['page' => $this])); + + if ($twig_first) { + if ($process_twig) { + $this->processTwig(); + } + if ($process_markdown) { + $this->processMarkdown(); + } + + // Content Processed but not cached yet + Grav::instance()->fireEvent('onPageContentProcessed', new Event(['page' => $this])); + } else { + if ($process_markdown) { + $this->processMarkdown($process_twig); + } + + // Content Processed but not cached yet + Grav::instance()->fireEvent('onPageContentProcessed', new Event(['page' => $this])); + + if ($process_twig) { + $this->processTwig(); + } + } + + if ($cache_enable) { + $this->cachePageContent(); + } + } + } + + // Handle summary divider + $delimiter = $config->get('site.summary.delimiter', '==='); + $divider_pos = mb_strpos($this->content, "

{$delimiter}

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

{$delimiter}

", '', $this->content); + } + + // Fire event when Page::content() is called + Grav::instance()->fireEvent('onPageContent', new Event(['page' => $this])); + } + + return $this->content; + } + + /** + * Get the contentMeta array and initialize content first if it's not already + * + * @return mixed + */ + public function contentMeta() + { + if ($this->content === null) { + $this->content(); + } + + return $this->getContentMeta(); + } + + /** + * Add an entry to the page's contentMeta array + * + * @param string $name + * @param mixed $value + */ + public function addContentMeta($name, $value) + { + $this->content_meta[$name] = $value; + } + + /** + * Return the whole contentMeta array as it currently stands + * + * @param string|null $name + * + * @return mixed|null + */ + public function getContentMeta($name = null) + { + if ($name) { + return $this->content_meta[$name] ?? null; + } + + return $this->content_meta; + } + + /** + * Sets the whole content meta array in one shot + * + * @param array $content_meta + * + * @return array + */ + public function setContentMeta($content_meta) + { + return $this->content_meta = $content_meta; + } + + /** + * Process the Markdown content. Uses Parsedown or Parsedown Extra depending on configuration + * + * @param bool $keepTwig If true, content between twig tags will not be processed. + * @return void + */ + protected function processMarkdown(bool $keepTwig = false) + { + /** @var Config $config */ + $config = Grav::instance()['config']; + + $markdownDefaults = (array)$config->get('system.pages.markdown'); + if (isset($this->header()->markdown)) { + $markdownDefaults = array_merge($markdownDefaults, $this->header()->markdown); + } + + // pages.markdown_extra is deprecated, but still check it... + if (!isset($markdownDefaults['extra']) && (isset($this->markdown_extra) || $config->get('system.pages.markdown_extra') !== null)) { + user_error('Configuration option \'system.pages.markdown_extra\' is deprecated since Grav 1.5, use \'system.pages.markdown.extra\' instead', E_USER_DEPRECATED); + + $markdownDefaults['extra'] = $this->markdown_extra ?: $config->get('system.pages.markdown_extra'); + } + + $extra = $markdownDefaults['extra'] ?? false; + $defaults = [ + 'markdown' => $markdownDefaults, + 'images' => $config->get('system.images', []) + ]; + + $excerpts = new Excerpts($this, $defaults); + + // Initialize the preferred variant of Parsedown + if ($extra) { + $parsedown = new ParsedownExtra($excerpts); + } else { + $parsedown = new Parsedown($excerpts); + } + + $content = $this->content; + if ($keepTwig) { + $token = [ + '/' . Utils::generateRandomString(3), + Utils::generateRandomString(3) . '/' + ]; + // Base64 encode any twig. + $content = preg_replace_callback( + ['/({#.*?#})/mu', '/({{.*?}})/mu', '/({%.*?%})/mu'], + static function ($matches) use ($token) { return $token[0] . base64_encode($matches[1]) . $token[1]; }, + $content + ); + } + + $content = $parsedown->text($content); + + if ($keepTwig) { + // Base64 decode the encoded twig. + $content = preg_replace_callback( + ['`' . $token[0] . '([A-Za-z0-9+/]+={0,2})' . $token[1] . '`mu'], + static function ($matches) { return base64_decode($matches[1]); }, + $content + ); + } + + $this->content = $content; + } + + + /** + * Process the Twig page content. + * + * @return void + */ + private function processTwig() + { + /** @var Twig $twig */ + $twig = Grav::instance()['twig']; + $this->content = $twig->processPage($this, $this->content); + } + + /** + * Fires the onPageContentProcessed event, and caches the page content using a unique ID for the page + * + * @return void + */ + public function cachePageContent() + { + /** @var Cache $cache */ + $cache = Grav::instance()['cache']; + $cache_id = md5('page' . $this->getCacheKey()); + $cache->save($cache_id, ['content' => $this->content, 'content_meta' => $this->content_meta]); + } + + /** + * Needed by the onPageContentProcessed event to get the raw page content + * + * @return string the current page content + */ + public function getRawContent() + { + return $this->content; + } + + /** + * Needed by the onPageContentProcessed event to set the raw page content + * + * @param string|null $content + * @return void + */ + public function setRawContent($content) + { + $this->content = $content ?? ''; + } + + /** + * Get value from a page variable (used mostly for creating edit forms). + * + * @param string $name Variable name. + * @param mixed $default + * @return mixed + */ + public function value($name, $default = null) + { + if ($name === 'content') { + return $this->raw_content; + } + if ($name === 'route') { + $parent = $this->parent(); + + return $parent ? $parent->rawRoute() : ''; + } + if ($name === 'order') { + $order = $this->order(); + + return $order ? (int)$this->order() : ''; + } + if ($name === 'ordering') { + return (bool)$this->order(); + } + if ($name === 'folder') { + return preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->folder); + } + if ($name === 'slug') { + return $this->slug(); + } + if ($name === 'name') { + $name = $this->name(); + $language = $this->language() ? '.' . $this->language() : ''; + $pattern = '%(' . preg_quote($language, '%') . ')?\.md$%'; + $name = preg_replace($pattern, '', $name); + + if ($this->isModule()) { + return 'modular/' . $name; + } + + return $name; + } + if ($name === 'media') { + return $this->media()->all(); + } + if ($name === 'media.file') { + return $this->media()->files(); + } + if ($name === 'media.video') { + return $this->media()->videos(); + } + if ($name === 'media.image') { + return $this->media()->images(); + } + if ($name === 'media.audio') { + return $this->media()->audios(); + } + + $path = explode('.', $name); + $scope = array_shift($path); + + if ($name === 'frontmatter') { + return $this->frontmatter; + } + + if ($scope === 'header') { + $current = $this->header(); + foreach ($path as $field) { + if (is_object($current) && isset($current->{$field})) { + $current = $current->{$field}; + } elseif (is_array($current) && isset($current[$field])) { + $current = $current[$field]; + } else { + return $default; + } + } + + return $current; + } + + return $default; + } + + /** + * Gets and Sets the Page raw content + * + * @param string|null $var + * @return string + */ + public function rawMarkdown($var = null) + { + if ($var !== null) { + $this->raw_content = $var; + } + + return $this->raw_content; + } + + /** + * @return bool + * @internal + */ + public function translated(): bool + { + return $this->initialized; + } + + /** + * Get file object to the page. + * + * @return MarkdownFile|null + */ + public function file() + { + if ($this->name) { + return MarkdownFile::instance($this->filePath()); + } + + return null; + } + + /** + * Save page if there's a file assigned to it. + * + * @param bool|array $reorder Internal use. + */ + public function save($reorder = true) + { + // Perform move, copy [or reordering] if needed. + $this->doRelocation(); + + $file = $this->file(); + if ($file) { + $file->filename($this->filePath()); + $file->header((array)$this->header()); + $file->markdown($this->raw_content); + $file->save(); + } + + // Perform reorder if required + if ($reorder && is_array($reorder)) { + $this->doReorder($reorder); + } + + // We need to signal Flex Pages about the change. + /** @var Flex|null $flex */ + $flex = Grav::instance()['flex'] ?? null; + $directory = $flex ? $flex->getDirectory('pages') : null; + if (null !== $directory) { + $directory->clearCache(); + } + + $this->_original = null; + } + + /** + * 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 PageInterface $parent New parent page. + * @return $this + */ + public function move(PageInterface $parent) + { + if (!$this->_original) { + $clone = clone $this; + $this->_original = $clone; + } + + $this->_action = 'move'; + + if ($this->route() === $parent->route()) { + throw new RuntimeException('Failed: Cannot set page parent to self'); + } + if (Utils::startsWith($parent->rawRoute(), $this->rawRoute())) { + throw new RuntimeException('Failed: Cannot set page parent to a child of current page'); + } + + $this->parent($parent); + $this->id(time() . md5($this->filePath())); + + if ($parent->path()) { + $this->path($parent->path() . '/' . $this->folder()); + } + + if ($parent->route()) { + $this->route($parent->route() . '/' . $this->slug()); + } else { + $this->route(Grav::instance()['pages']->root()->route() . '/' . $this->slug()); + } + + $this->raw_route = null; + + return $this; + } + + /** + * Prepare a copy from the page. Copies also everything that's under the current page. + * + * Returns a new Page object for the copy. + * You need to call $this->save() in order to perform the move. + * + * @param PageInterface $parent New parent page. + * @return $this + */ + public function copy(PageInterface $parent) + { + $this->move($parent); + $this->_action = 'copy'; + + return $this; + } + + /** + * Get blueprints for the page. + * + * @return Blueprint + */ + public function blueprints() + { + $grav = Grav::instance(); + + /** @var Pages $pages */ + $pages = $grav['pages']; + + $blueprint = $pages->blueprints($this->blueprintName()); + $fields = $blueprint->fields(); + $edit_mode = isset($grav['admin']) ? $grav['config']->get('plugins.admin.edit_mode') : null; + + // override if you only want 'normal' mode + if (empty($fields) && ($edit_mode === 'auto' || $edit_mode === 'normal')) { + $blueprint = $pages->blueprints('default'); + } + + // override if you only want 'expert' mode + if (!empty($fields) && $edit_mode === 'expert') { + $blueprint = $pages->blueprints(''); + } + + return $blueprint; + } + + /** + * Returns the blueprint from the page. + * + * @param string $name Not used. + * @return Blueprint Returns a Blueprint. + */ + public function getBlueprint(string $name = '') + { + return $this->blueprints(); + } + + /** + * Get the blueprint name for this page. Use the blueprint form field if set + * + * @return string + */ + public function blueprintName() + { + if (!isset($_POST['blueprint'])) { + return $this->template(); + } + + $post_value = $_POST['blueprint']; + $sanitized_value = htmlspecialchars(strip_tags($post_value), ENT_QUOTES, 'UTF-8'); + + return $sanitized_value ?: $this->template(); + } + + /** + * Validate page header. + * + * @return void + * @throws Exception + */ + public function validate() + { + $blueprints = $this->blueprints(); + $blueprints->validate($this->toArray()); + } + + /** + * Filter page header from illegal contents. + * + * @return void + */ + public function filter() + { + $blueprints = $this->blueprints(); + $values = $blueprints->filter($this->toArray()); + if ($values && isset($values['header'])) { + $this->header($values['header']); + } + } + + /** + * Get unknown header variables. + * + * @return array + */ + public function extra() + { + $blueprints = $this->blueprints(); + + return $blueprints->extra($this->toArray()['header'], 'header.'); + } + + /** + * Convert page to an array. + * + * @return array + */ + public function toArray() + { + return [ + 'header' => (array)$this->header(), + 'content' => (string)$this->value('content') + ]; + } + + /** + * Convert page to YAML encoded string. + * + * @return string + */ + public function toYaml() + { + return Yaml::dump($this->toArray(), 20); + } + + /** + * Convert page to JSON encoded string. + * + * @return string + */ + public function toJson() + { + return json_encode($this->toArray()); + } + + /** + * @return string + */ + public function getCacheKey(): string + { + return $this->id(); + } + + /** + * Gets and sets the associated media as found in the page folder. + * + * @param Media|null $var Representation of associated media. + * @return Media Representation of associated media. + */ + public function media($var = null) + { + if ($var) { + $this->setMedia($var); + } + + /** @var Media $media */ + $media = $this->getMedia(); + + return $media; + } + + /** + * Get filesystem path to the associated media. + * + * @return string|null + */ + public function getMediaFolder() + { + return $this->path(); + } + + /** + * Get display order for the associated media. + * + * @return array Empty array means default ordering. + */ + public function getMediaOrder() + { + $header = $this->header(); + + return isset($header->media_order) ? array_map('trim', explode(',', (string)$header->media_order)) : []; + } + + /** + * Gets and sets the name field. If no name field is set, it will return 'default.md'. + * + * @param string|null $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 $this->name ?: 'default.md'; + } + + /** + * Returns child page type. + * + * @return string + */ + public function childType() + { + return isset($this->header->child_type) ? (string)$this->header->child_type : ''; + } + + /** + * 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|null $var the template name + * @return string the template name + */ + public function template($var = null) + { + if ($var !== null) { + $this->template = $var; + } + if (empty($this->template)) { + $this->template = ($this->isModule() ? 'modular/' : '') . str_replace($this->extension(), '', $this->name()); + } + + return $this->template; + } + + /** + * Allows a page to override the output render format, usually the extension provided in the URL. + * (e.g. `html`, `json`, `xml`, etc). + * + * @param string|null $var + * @return string + */ + public function templateFormat($var = null) + { + if (null !== $var) { + $this->template_format = is_string($var) ? $var : null; + } + + if (!isset($this->template_format)) { + $this->template_format = ltrim($this->header->append_url_extension ?? Utils::getPageFormat(), '.'); + } + + return $this->template_format; + } + + /** + * Gets and sets the extension field. + * + * @param string|null $var + * @return string + */ + public function extension($var = null) + { + if ($var !== null) { + $this->extension = $var; + } + if (empty($this->extension)) { + $this->extension = '.' . Utils::pathinfo($this->name(), PATHINFO_EXTENSION); + } + + return $this->extension; + } + + /** + * Returns the page extension, got from the page `url_extension` config and falls back to the + * system config `system.pages.append_url_extension`. + * + * @return string The extension of this page. For example `.html` + */ + public function urlExtension() + { + if ($this->home()) { + return ''; + } + + // if not set in the page get the value from system config + if (null === $this->url_extension) { + $this->url_extension = Grav::instance()['config']->get('system.pages.append_url_extension', ''); + } + + return $this->url_extension; + } + + /** + * Gets and sets the expires field. If not set will return the default + * + * @param int|null $var The new expires value. + * @return int The expires value + */ + public function expires($var = null) + { + if ($var !== null) { + $this->expires = $var; + } + + return $this->expires ?? Grav::instance()['config']->get('system.pages.expires'); + } + + /** + * Gets and sets the cache-control property. If not set it will return the default value (null) + * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control for more details on valid options + * + * @param string|null $var + * @return string|null + */ + public function cacheControl($var = null) + { + if ($var !== null) { + $this->cache_control = $var; + } + + return $this->cache_control ?? Grav::instance()['config']->get('system.pages.cache_control'); + } + + /** + * Gets and sets the title for this Page. If no title is set, it will use the slug() to get a name + * + * @param string|null $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|null $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|null $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 + if (preg_match(PAGE_ORDER_PREFIX_REGEX, $this->folder)) { + $this->visible = true; + } else { + $this->visible = false; + } + } + + return $this->visible; + } + + /** + * Gets and Sets whether or not this Page is considered published + * + * @param bool|null $var true if the page is published + * @return bool true if the page is published + */ + public function published($var = null) + { + if ($var !== null) { + $this->published = (bool)$var; + } + + // If not published, should not be visible in menus either + if ($this->published === false) { + $this->visible = false; + } + + return $this->published; + } + + /** + * Gets and Sets the Page publish date + * + * @param string|null $var string representation of a date + * @return int unix timestamp representation of the date + */ + public function publishDate($var = null) + { + if ($var !== null) { + $this->publish_date = Utils::date2timestamp($var, $this->dateformat); + } + + return $this->publish_date; + } + + /** + * Gets and Sets the Page unpublish date + * + * @param string|null $var string representation of a date + * @return int|null unix timestamp representation of the date + */ + public function unpublishDate($var = null) + { + if ($var !== null) { + $this->unpublish_date = Utils::date2timestamp($var, $this->dateformat); + } + + return $this->unpublish_date; + } + + /** + * Gets and Sets whether or not this Page is routable, ie you can reach it + * via a URL. + * The page must be *routable* and *published* + * + * @param bool|null $var true if the page is routable + * @return bool true if the page is routable + */ + public function routable($var = null) + { + if ($var !== null) { + $this->routable = (bool)$var; + } + + return $this->routable && $this->published(); + } + + /** + * @param bool|null $var + * @return bool + */ + public function ssl($var = null) + { + if ($var !== null) { + $this->ssl = (bool)$var; + } + + return $this->ssl; + } + + /** + * Gets and Sets the process setup for this Page. This is multi-dimensional array that consists of + * a simple array of arrays with the form array("markdown"=>true) for example + * + * @param array|null $var an Array of name value pairs where the name is the process and value is true or false + * @return array an Array of name value pairs where the name is the process and value is true or false + */ + public function process($var = null) + { + if ($var !== null) { + $this->process = (array)$var; + } + + return $this->process; + } + + /** + * Returns the state of the debugger override setting for this page + * + * @return bool + */ + public function debugger() + { + return !(isset($this->debugger) && $this->debugger === false); + } + + /** + * Function to merge page metadata tags and build an array of Metadata objects + * that can then be rendered in the page. + * + * @param array|null $var an Array of metadata values to set + * @return array an Array of metadata values for the page + */ + public function metadata($var = null) + { + if ($var !== null) { + $this->metadata = (array)$var; + } + + // if not metadata yet, process it. + if (null === $this->metadata) { + $header_tag_http_equivs = ['content-type', 'default-style', 'refresh', 'x-ua-compatible', 'content-security-policy']; + + $this->metadata = []; + + // Set the Generator tag + $metadata = [ + 'generator' => 'GravCMS' + ]; + + $config = Grav::instance()['config']; + + $escape = !$config->get('system.strict_mode.twig_compat', false) || $config->get('system.twig.autoescape', true); + + // Get initial metadata for the page + $metadata = array_merge($metadata, $config->get('site.metadata', [])); + + if (isset($this->header->metadata) && is_array($this->header->metadata)) { + // Merge any site.metadata settings in with page metadata + $metadata = array_merge($metadata, $this->header->metadata); + } + + // Build an array of meta objects.. + foreach ((array)$metadata as $key => $value) { + // Lowercase the key + $key = strtolower($key); + // If this is a property type metadata: "og", "twitter", "facebook" etc + // Backward compatibility for nested arrays in metas + if (is_array($value)) { + foreach ($value as $property => $prop_value) { + $prop_key = $key . ':' . $property; + $this->metadata[$prop_key] = [ + 'name' => $prop_key, + 'property' => $prop_key, + 'content' => $escape ? htmlspecialchars($prop_value, ENT_QUOTES | ENT_HTML5, 'UTF-8') : $prop_value + ]; + } + } else { + // If it this is a standard meta data type + if ($value) { + if (in_array($key, $header_tag_http_equivs, true)) { + $this->metadata[$key] = [ + 'http_equiv' => $key, + 'content' => $escape ? htmlspecialchars($value, ENT_COMPAT, 'UTF-8') : $value + ]; + } elseif ($key === 'charset') { + $this->metadata[$key] = ['charset' => $escape ? htmlspecialchars($value, ENT_QUOTES | ENT_HTML5, 'UTF-8') : $value]; + } else { + // if it's a social metadata with separator, render as property + $separator = strpos($key, ':'); + $hasSeparator = $separator && $separator < strlen($key) - 1; + $entry = [ + 'content' => $escape ? htmlspecialchars($value, ENT_QUOTES | ENT_HTML5, 'UTF-8') : $value + ]; + + if ($hasSeparator && !Utils::startsWith($key, ['twitter', 'flattr','fediverse'])) { + $entry['property'] = $key; + } else { + $entry['name'] = $key; + } + + $this->metadata[$key] = $entry; + } + } + } + } + } + + return $this->metadata; + } + + /** + * Reset the metadata and pull from header again + */ + public function resetMetadata() + { + $this->metadata = null; + } + + /** + * 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|null $var the slug, e.g. 'my-blog' + * @return string the slug + */ + public function slug($var = null) + { + if ($var !== null && $var !== '') { + $this->slug = $var; + } + + if (empty($this->slug)) { + $this->slug = $this->adjustRouteCase(preg_replace(PAGE_ORDER_PREFIX_REGEX, '', (string) $this->folder)) ?: null; + } + + return $this->slug; + } + + /** + * Get/set order number of this page. + * + * @param int|null $var + * @return string|bool + */ + public function order($var = null) + { + if ($var !== null) { + $order = $var ? sprintf('%02d.', (int)$var) : ''; + $this->folder($order . preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->folder)); + + return $order; + } + + preg_match(PAGE_ORDER_PREFIX_REGEX, $this->folder, $order); + + return $order[0] ?? false; + } + + /** + * 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 with host information, aka Permalink. + * @return string The permalink. + */ + public function permalink() + { + return $this->url(true, false, true, true); + } + + /** + * Returns the canonical URL for a page + * + * @param bool $include_lang + * @return string + */ + public function canonical($include_lang = true) + { + return $this->url(true, true, $include_lang); + } + + /** + * Gets the url for the Page. + * + * @param bool $include_host Defaults false, but true would include http://yourhost.com + * @param bool $canonical True to return the canonical URL + * @param bool $include_base Include base url on multisite as well as language code + * @param bool $raw_route + * @return string The url. + */ + public function url($include_host = false, $canonical = false, $include_base = true, $raw_route = false) + { + // Override any URL when external_url is set + if (isset($this->external_url)) { + return $this->external_url; + } + + $grav = Grav::instance(); + + /** @var Pages $pages */ + $pages = $grav['pages']; + + /** @var Config $config */ + $config = $grav['config']; + + // get base route (multi-site base and language) + $route = $include_base ? $pages->baseRoute() : ''; + + // add full route if configured to do so + if (!$include_host && $config->get('system.absolute_urls', false)) { + $include_host = true; + } + + if ($canonical) { + $route .= $this->routeCanonical(); + } elseif ($raw_route) { + $route .= $this->rawRoute(); + } else { + $route .= $this->route(); + } + + /** @var Uri $uri */ + $uri = $grav['uri']; + $url = $uri->rootUrl($include_host) . '/' . trim($route, '/') . $this->urlExtension(); + + return Uri::filterPath($url); + } + + /** + * Gets the route for the page based on the route headers if available, else from + * the parents route and the current Page's slug. + * + * @param string|null $var Set new default route. + * @return string|null The route for the Page. + */ + public function route($var = null) + { + if ($var !== null) { + $this->route = $var; + } + + if (empty($this->route)) { + $baseRoute = null; + + // calculate route based on parent slugs + $parent = $this->parent(); + if (isset($parent)) { + if ($this->hide_home_route && $parent->route() === $this->home_route) { + $baseRoute = ''; + } else { + $baseRoute = (string)$parent->route(); + } + } + + $this->route = isset($baseRoute) ? $baseRoute . '/' . $this->slug() : null; + + if (!empty($this->routes) && isset($this->routes['default'])) { + $this->routes['aliases'][] = $this->route; + $this->route = $this->routes['default']; + + return $this->route; + } + } + + return $this->route; + } + + /** + * Helper method to clear the route out so it regenerates next time you use it + */ + public function unsetRouteSlug() + { + unset($this->route, $this->slug); + } + + /** + * Gets and Sets the page raw route + * + * @param string|null $var + * @return null|string + */ + public function rawRoute($var = null) + { + if ($var !== null) { + $this->raw_route = $var; + } + + if (empty($this->raw_route)) { + $parent = $this->parent(); + $baseRoute = $parent ? (string)$parent->rawRoute() : null; + + $slug = $this->adjustRouteCase(preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->folder)); + + $this->raw_route = isset($baseRoute) ? $baseRoute . '/' . $slug : null; + } + + return $this->raw_route; + } + + /** + * Gets the route aliases for the page based on page headers. + * + * @param array|null $var list of route aliases + * @return array The route aliases for the Page. + */ + public function routeAliases($var = null) + { + if ($var !== null) { + $this->routes['aliases'] = (array)$var; + } + + if (!empty($this->routes) && isset($this->routes['aliases'])) { + return $this->routes['aliases']; + } + + return []; + } + + /** + * Gets the canonical route for this page if its set. If provided it will use + * that value, else if it's `true` it will use the default route. + * + * @param string|null $var + * @return bool|string + */ + public function routeCanonical($var = null) + { + if ($var !== null) { + $this->routes['canonical'] = $var; + } + + if (!empty($this->routes) && isset($this->routes['canonical'])) { + return $this->routes['canonical']; + } + + return $this->route(); + } + + /** + * Gets and sets the identifier for this Page object. + * + * @param string|null $var the identifier + * @return string the identifier + */ + public function id($var = null) + { + if (null === $this->id) { + // We need to set unique id to avoid potential cache conflicts between pages. + $var = time() . md5($this->filePath()); + } + if ($var !== null) { + // store unique per language + $active_lang = Grav::instance()['language']->getLanguage() ?: ''; + $id = $active_lang . $var; + $this->id = $id; + } + + return $this->id; + } + + /** + * Gets and sets the modified timestamp. + * + * @param int|null $var modified unix timestamp + * @return int modified unix timestamp + */ + public function modified($var = null) + { + if ($var !== null) { + $this->modified = $var; + } + + return $this->modified; + } + + /** + * Gets the redirect set in the header. + * + * @param string|null $var redirect url + * @return string|null + */ + public function redirect($var = null) + { + if ($var !== null) { + $this->redirect = $var; + } + + return $this->redirect ?: null; + } + + /** + * Gets and sets the option to show the etag header for the page. + * + * @param bool|null $var show etag header + * @return bool show etag header + */ + public function eTag($var = null): bool + { + if ($var !== null) { + $this->etag = $var; + } + if (!isset($this->etag)) { + $this->etag = (bool)Grav::instance()['config']->get('system.pages.etag'); + } + + return $this->etag ?? false; + } + + /** + * Gets and sets the option to show the last_modified header for the page. + * + * @param bool|null $var show last_modified header + * @return bool show last_modified header + */ + public function lastModified($var = null) + { + if ($var !== null) { + $this->last_modified = $var; + } + if (!isset($this->last_modified)) { + $this->last_modified = (bool)Grav::instance()['config']->get('system.pages.last_modified'); + } + + return $this->last_modified; + } + + /** + * Gets and sets the path to the .md file for this Page object. + * + * @param string|null $var the file path + * @return string|null the file path + */ + public function filePath($var = null) + { + if ($var !== null) { + // Filename of the page. + $this->name = Utils::basename($var); + // Folder of the page. + $this->folder = Utils::basename(dirname($var)); + // Path to the page. + $this->path = dirname($var, 2); + } + + return rtrim($this->path . '/' . $this->folder . '/' . ($this->name() ?: ''), '/'); + } + + /** + * Gets the relative path to the .md file + * + * @return string The relative file path + */ + public function filePathClean() + { + return str_replace(GRAV_ROOT . DS, '', $this->filePath()); + } + + /** + * Returns the clean path to the page file + * + * @return string + */ + public function relativePagePath() + { + return str_replace('/' . $this->name(), '', $this->filePathClean()); + } + + /** + * 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|null $var the path + * @return string|null the path + */ + public function path($var = null) + { + if ($var !== null) { + // Folder of the page. + $this->folder = Utils::basename($var); + // Path to the page. + $this->path = dirname($var); + } + + return $this->path ? $this->path . '/' . $this->folder : null; + } + + /** + * Get/set the folder. + * + * @param string|null $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|null $var string representation of a date + * @return int unix timestamp representation of the date + */ + public function date($var = null) + { + if ($var !== null) { + $this->date = Utils::date2timestamp($var, $this->dateformat); + } + + if (!$this->date) { + $this->date = $this->modified; + } + + return $this->date; + } + + /** + * Gets and sets the date format for this Page object. This is typically passed in via the page headers + * using typical PHP date string structure - http://php.net/manual/en/function.date.php + * + * @param string|null $var string representation of a date format + * @return string string representation of a date format + */ + public function dateformat($var = null) + { + if ($var !== null) { + $this->dateformat = $var; + } + + return $this->dateformat; + } + + /** + * Gets and sets the order by which any sub-pages should be sorted. + * + * @param string|null $var the order, either "asc" or "desc" + * @return string the order, either "asc" or "desc" + * @deprecated 1.6 + */ + public function orderDir($var = null) + { + //user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6', E_USER_DEPRECATED); + + 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|null $var supported options include "default", "title", "date", and "folder" + * @return string supported options include "default", "title", "date", and "folder" + * @deprecated 1.6 + */ + public function orderBy($var = null) + { + //user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6', E_USER_DEPRECATED); + + if ($var !== null) { + $this->order_by = $var; + } + + return $this->order_by; + } + + /** + * Gets the manual order set in the header. + * + * @param string|null $var supported options include "default", "title", "date", and "folder" + * @return array + * @deprecated 1.6 + */ + public function orderManual($var = null) + { + //user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6', E_USER_DEPRECATED); + + 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|null $var the maximum number of sub-pages + * @return int the maximum number of sub-pages + * @deprecated 1.6 + */ + public function maxCount($var = null) + { + //user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6', E_USER_DEPRECATED); + + if ($var !== null) { + $this->max_count = (int)$var; + } + if (empty($this->max_count)) { + /** @var Config $config */ + $config = Grav::instance()['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|null $var an array of taxonomies + * @return array an array of taxonomies + */ + public function taxonomy($var = null) + { + if ($var !== null) { + // make sure first level are arrays + array_walk($var, static function (&$value) { + $value = (array) $value; + }); + // make sure all values are strings + array_walk_recursive($var, static function (&$value) { + $value = (string) $value; + }); + $this->taxonomy = $var; + } + + return $this->taxonomy; + } + + /** + * Gets and sets the modular var that helps identify this page is a modular child + * + * @param bool|null $var true if modular_twig + * @return bool true if modular_twig + * @deprecated 1.7 Use ->isModule() or ->modularTwig() method instead. + */ + public function modular($var = null) + { + user_error(__METHOD__ . '() is deprecated since Grav 1.7, use ->isModule() or ->modularTwig() method instead', E_USER_DEPRECATED); + + return $this->modularTwig($var); + } + + /** + * Gets and sets the modular_twig var that helps identify this page as a modular child page that will need + * twig processing handled differently from a regular page. + * + * @param bool|null $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->visible(false); + // some routable logic + if (empty($this->header->routable)) { + $this->routable = false; + } + } + } + + return $this->modular_twig ?? false; + } + + /** + * 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 (bool)($this->process[$process] ?? false); + } + + /** + * Gets and Sets the parent object for this page + * + * @param PageInterface|null $var the parent page object + * @return PageInterface|null the parent page object if it exists. + */ + public function parent(PageInterface $var = null) + { + if ($var) { + $this->parent = $var->path(); + + return $var; + } + + /** @var Pages $pages */ + $pages = Grav::instance()['pages']; + + return $pages->get($this->parent); + } + + /** + * Gets the top parent object for this page. Can return page itself. + * + * @return PageInterface The top parent page object. + */ + public function topParent() + { + $topParent = $this; + + while (true) { + $theParent = $topParent->parent(); + if ($theParent !== null && $theParent->parent() !== null) { + $topParent = $theParent; + } else { + break; + } + } + + return $topParent; + } + + /** + * Returns children of this page. + * + * @return PageCollectionInterface|Collection + */ + public function children() + { + /** @var Pages $pages */ + $pages = Grav::instance()['pages']; + + return $pages->children($this->path()); + } + + + /** + * Check to see if this item is the first in an array of sub-pages. + * + * @return bool True if item is first. + */ + public function isFirst() + { + $parent = $this->parent(); + $collection = $parent ? $parent->collection('content', false) : null; + if ($collection instanceof Collection) { + return $collection->isFirst($this->path()); + } + + return true; + } + + /** + * Check to see if this item is the last in an array of sub-pages. + * + * @return bool True if item is last + */ + public function isLast() + { + $parent = $this->parent(); + $collection = $parent ? $parent->collection('content', false) : null; + if ($collection instanceof Collection) { + return $collection->isLast($this->path()); + } + + return true; + } + + /** + * Gets the previous sibling based on current position. + * + * @return PageInterface the previous Page item + */ + public function prevSibling() + { + return $this->adjacentSibling(-1); + } + + /** + * Gets the next sibling based on current position. + * + * @return PageInterface the next Page item + */ + public function nextSibling() + { + return $this->adjacentSibling(1); + } + + /** + * Returns the adjacent sibling based on a direction. + * + * @param int $direction either -1 or +1 + * @return PageInterface|false the sibling page + */ + public function adjacentSibling($direction = 1) + { + $parent = $this->parent(); + $collection = $parent ? $parent->collection('content', false) : null; + if ($collection instanceof Collection) { + return $collection->adjacentSibling($this->path(), $direction); + } + + return false; + } + + /** + * Returns the item in the current position. + * + * @return int|null The index of the current page. + */ + public function currentPosition() + { + $parent = $this->parent(); + $collection = $parent ? $parent->collection('content', false) : null; + if ($collection instanceof Collection) { + return $collection->currentPosition($this->path()); + } + + return 1; + } + + /** + * Returns whether or not this page is the currently active page requested via the URL. + * + * @return bool True if it is active + */ + public function active() + { + $uri_path = rtrim(urldecode(Grav::instance()['uri']->path()), '/') ?: '/'; + $routes = Grav::instance()['pages']->routes(); + + return isset($routes[$uri_path]) && $routes[$uri_path] === $this->path(); + } + + /** + * 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() + { + $grav = Grav::instance(); + /** @var Uri $uri */ + $uri = $grav['uri']; + /** @var Pages $pages */ + $pages = $grav['pages']; + $uri_path = rtrim(urldecode($uri->path()), '/'); + $routes = $pages->routes(); + + if (isset($routes[$uri_path])) { + $page = $pages->find($uri->route()); + /** @var PageInterface|null $child_page */ + $child_page = $page ? $page->parent() : null; + while ($child_page && !$child_page->root()) { + if ($this->path() === $child_page->path()) { + return true; + } + $child_page = $child_page->parent(); + } + } + + return false; + } + + /** + * Returns whether or not this page is the currently configured home page. + * + * @return bool True if it is the homepage + */ + public function home() + { + $home = Grav::instance()['config']->get('system.home.alias'); + + return $this->route() === $home || $this->rawRoute() === $home; + } + + /** + * 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() + { + return !$this->parent && !$this->name && !$this->visible; + } + + /** + * Helper method to return an ancestor page. + * + * @param bool|null $lookup Name of the parent folder + * @return PageInterface page you were looking for if it exists + */ + public function ancestor($lookup = null) + { + /** @var Pages $pages */ + $pages = Grav::instance()['pages']; + + return $pages->ancestor($this->route, $lookup); + } + + /** + * Helper method to return an ancestor page to inherit from. The current + * page object is returned. + * + * @param string $field Name of the parent folder + * @return PageInterface + */ + public function inherited($field) + { + [$inherited, $currentParams] = $this->getInheritedParams($field); + + $this->modifyHeader($field, $currentParams); + + return $inherited; + } + + /** + * Helper method to return an ancestor field only to inherit from. The + * first occurrence of an ancestor field will be returned if at all. + * + * @param string $field Name of the parent folder + * + * @return array + */ + public function inheritedField($field) + { + [$inherited, $currentParams] = $this->getInheritedParams($field); + + return $currentParams; + } + + /** + * Method that contains shared logic for inherited() and inheritedField() + * + * @param string $field Name of the parent folder + * @return array + */ + protected function getInheritedParams($field) + { + $pages = Grav::instance()['pages']; + + /** @var Pages $pages */ + $inherited = $pages->inherited($this->route, $field); + $inheritedParams = $inherited ? (array)$inherited->value('header.' . $field) : []; + $currentParams = (array)$this->value('header.' . $field); + if ($inheritedParams && is_array($inheritedParams)) { + $currentParams = array_replace_recursive($inheritedParams, $currentParams); + } + + return [$inherited, $currentParams]; + } + + /** + * Helper method to return a page. + * + * @param string $url the url of the page + * @param bool $all + * + * @return PageInterface page you were looking for if it exists + */ + public function find($url, $all = false) + { + /** @var Pages $pages */ + $pages = Grav::instance()['pages']; + + return $pages->find($url, $all); + } + + /** + * Get a collection of pages in the current context. + * + * @param string|array $params + * @param bool $pagination + * + * @return PageCollectionInterface|Collection + * @throws InvalidArgumentException + */ + public function collection($params = 'content', $pagination = true) + { + if (is_string($params)) { + // Look into a page header field. + $params = (array)$this->value('header.' . $params); + } elseif (!is_array($params)) { + throw new InvalidArgumentException('Argument should be either header variable name or array of parameters'); + } + + $params['filter'] = ($params['filter'] ?? []) + ['translated' => true]; + $context = [ + 'pagination' => $pagination, + 'self' => $this + ]; + + /** @var Pages $pages */ + $pages = Grav::instance()['pages']; + + return $pages->getCollection($params, $context); + } + + /** + * @param string|array $value + * @param bool $only_published + * @return PageCollectionInterface|Collection + */ + public function evaluate($value, $only_published = true) + { + $params = [ + 'items' => $value, + 'published' => $only_published + ]; + $context = [ + 'event' => false, + 'pagination' => false, + 'url_taxonomy_filters' => false, + 'self' => $this + ]; + + /** @var Pages $pages */ + $pages = Grav::instance()['pages']; + + return $pages->getCollection($params, $context); + } + + /** + * 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(); + } + + /** + * @return bool + */ + public function isModule(): bool + { + return $this->modularTwig(); + } + + /** + * Returns whether the page exists in the filesystem. + * + * @return bool + */ + public function exists() + { + $file = $this->file(); + + return $file && $file->exists(); + } + + /** + * Returns whether or not the current folder exists + * + * @return bool + */ + public function folderExists() + { + return file_exists($this->path()); + } + + /** + * 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; + } + + /** + * Reorders all siblings according to a defined order + * + * @param array|null $new_order + */ + protected function doReorder($new_order) + { + if (!$this->_original) { + return; + } + + $pages = Grav::instance()['pages']; + $pages->init(); + + $this->_original->path($this->path()); + + $parent = $this->parent(); + $siblings = $parent ? $parent->children() : null; + + if ($siblings) { + $siblings->order('slug', 'asc', $new_order); + + $counter = 0; + + // Reorder all moved pages. + foreach ($siblings as $slug => $page) { + $order = (int)trim($page->order(), '.'); + $counter++; + + if ($order) { + if ($page->path() === $this->path() && $this->folderExists()) { + // Handle current page; we do want to change ordering number, but nothing else. + $this->order($counter); + $this->save(false); + } else { + // Handle all the other pages. + $page = $pages->get($page->path()); + if ($page && $page->folderExists() && !$page->_action) { + $page = $page->move($this->parent()); + $page->order($counter); + $page->save(false); + } + } + } + } + } + } + + /** + * Moves or copies the page in filesystem. + * + * @internal + * @return void + * @throws Exception + */ + protected function doRelocation() + { + if (!$this->_original) { + return; + } + + if (is_dir($this->_original->path())) { + if ($this->_action === 'move') { + Folder::move($this->_original->path(), $this->path()); + } elseif ($this->_action === 'copy') { + Folder::copy($this->_original->path(), $this->path()); + } + } + + if ($this->name() !== $this->_original->name()) { + $path = $this->path(); + if (is_file($path . '/' . $this->_original->name())) { + rename($path . '/' . $this->_original->name(), $path . '/' . $this->name()); + } + } + } + + /** + * @return void + */ + protected function setPublishState() + { + // Handle publishing dates if no explicit published option set + if (Grav::instance()['config']->get('system.pages.publish_dates') && !isset($this->header->published)) { + // unpublish if required, if not clear cache right before page should be unpublished + if ($this->unpublishDate()) { + if ($this->unpublishDate() < time()) { + $this->published(false); + } else { + $this->published(); + Grav::instance()['cache']->setLifeTime($this->unpublishDate()); + } + } + // publish if required, if not clear cache right before page is published + if ($this->publishDate() && $this->publishDate() > time()) { + $this->published(false); + Grav::instance()['cache']->setLifeTime($this->publishDate()); + } + } + } + + /** + * @param string $route + * @return string + */ + protected function adjustRouteCase($route) + { + $case_insensitive = Grav::instance()['config']->get('system.force_lowercase_urls'); + + return $case_insensitive ? mb_strtolower($route) : $route; + } + + /** + * Gets the Page Unmodified (original) version of the page. + * + * @return PageInterface The original version of the page. + */ + public function getOriginal() + { + return $this->_original; + } + + /** + * Gets the action. + * + * @return string|null The Action string. + */ + public function getAction() + { + return $this->_action; + } +} diff --git a/system/src/Grav/Common/Page/Pages.php b/system/src/Grav/Common/Page/Pages.php new file mode 100644 index 0000000..9dd8ee6 --- /dev/null +++ b/system/src/Grav/Common/Page/Pages.php @@ -0,0 +1,2258 @@ + */ + protected $instances = []; + /** @var array */ + protected $index = []; + /** @var array */ + protected $children; + /** @var string */ + protected $base = ''; + /** @var string[] */ + protected $baseRoute = []; + /** @var string[] */ + protected $routes = []; + /** @var array */ + protected $sort; + /** @var Blueprints */ + protected $blueprints; + /** @var bool */ + protected $enable_pages = true; + /** @var int */ + protected $last_modified; + /** @var string[] */ + protected $ignore_files; + /** @var string[] */ + protected $ignore_folders; + /** @var bool */ + protected $ignore_hidden; + /** @var string */ + protected $check_method; + /** @var string */ + protected $simple_pages_hash; + /** @var string */ + protected $pages_cache_id; + /** @var bool */ + protected $initialized = false; + /** @var string */ + protected $active_lang; + /** @var bool */ + protected $fire_events = false; + /** @var Types|null */ + protected static $types; + /** @var string|null */ + protected static $home_route; + + + /** + * Constructor + * + * @param Grav $grav + */ + public function __construct(Grav $grav) + { + $this->grav = $grav; + } + + /** + * @return FlexDirectory|null + */ + public function getDirectory(): ?FlexDirectory + { + return $this->directory; + } + + /** + * Method used in admin to disable frontend pages from being initialized. + */ + public function disablePages(): void + { + $this->enable_pages = false; + } + + /** + * Method used in admin to later load frontend pages. + */ + public function enablePages(): void + { + if (!$this->enable_pages) { + $this->enable_pages = true; + + $this->init(); + } + } + + /** + * Get or set base path for the pages. + * + * @param string|null $path + * @return string + */ + public function base($path = null) + { + if ($path !== null) { + $path = trim($path, '/'); + $this->base = $path ? '/' . $path : ''; + $this->baseRoute = []; + } + + return $this->base; + } + + /** + * + * Get base route for Grav pages. + * + * @param string|null $lang Optional language code for multilingual routes. + * @return string + */ + public function baseRoute($lang = null) + { + $key = $lang ?: $this->active_lang ?: 'default'; + + if (!isset($this->baseRoute[$key])) { + /** @var Language $language */ + $language = $this->grav['language']; + + $path_base = rtrim($this->base(), '/'); + $path_lang = $language->enabled() ? $language->getLanguageURLPrefix($lang) : ''; + + $this->baseRoute[$key] = $path_base . $path_lang; + } + + return $this->baseRoute[$key]; + } + + /** + * + * Get route for Grav site. + * + * @param string $route Optional route to the page. + * @param string|null $lang Optional language code for multilingual links. + * @return string + */ + public function route($route = '/', $lang = null) + { + if (!$route || $route === '/') { + return $this->baseRoute($lang) ?: '/'; + } + + return $this->baseRoute($lang) . $route; + } + + /** + * Get relative referrer route and language code. Returns null if the route isn't within the current base, language (if set) and route. + * + * @example `$langCode = null; $referrer = $pages->referrerRoute($langCode, '/admin');` returns relative referrer url within /admin and updates $langCode + * @example `$langCode = 'en'; $referrer = $pages->referrerRoute($langCode, '/admin');` returns relative referrer url within the /en/admin + * + * @param string|null $langCode Variable to store the language code. If already set, check only against that language. + * @param string $route Optional route within the site. + * @return string|null + * @since 1.7.23 + */ + public function referrerRoute(?string &$langCode, string $route = '/'): ?string + { + $referrer = $_SERVER['HTTP_REFERER'] ?? null; + + // Start by checking that referrer came from our site. + $root = $this->grav['base_url_absolute']; + if (!is_string($referrer) || !str_starts_with($referrer, $root)) { + return null; + } + + /** @var Language $language */ + $language = $this->grav['language']; + + // Get all language codes and append no language. + if (null === $langCode) { + $languages = $language->enabled() ? $language->getLanguages() : []; + $languages[] = ''; + } else { + $languages[] = $langCode; + } + + $path_base = rtrim($this->base(), '/'); + $path_route = rtrim($route, '/'); + + // Try to figure out the language code. + foreach ($languages as $code) { + $path_lang = $code ? "/{$code}" : ''; + + $base = $path_base . $path_lang . $path_route; + if ($referrer === $base || str_starts_with($referrer, "{$base}/")) { + if (null === $langCode) { + $langCode = $code; + } + + return substr($referrer, \strlen($base)); + } + } + + return null; + } + + /** + * + * Get base URL for Grav pages. + * + * @param string|null $lang Optional language code for multilingual links. + * @param bool|null $absolute If true, return absolute url, if false, return relative url. Otherwise return default. + * @return string + */ + public function baseUrl($lang = null, $absolute = null) + { + if ($absolute === null) { + $type = 'base_url'; + } elseif ($absolute) { + $type = 'base_url_absolute'; + } else { + $type = 'base_url_relative'; + } + + return $this->grav[$type] . $this->baseRoute($lang); + } + + /** + * + * Get home URL for Grav site. + * + * @param string|null $lang Optional language code for multilingual links. + * @param bool|null $absolute If true, return absolute url, if false, return relative url. Otherwise return default. + * @return string + */ + public function homeUrl($lang = null, $absolute = null) + { + return $this->baseUrl($lang, $absolute) ?: '/'; + } + + /** + * + * Get URL for Grav site. + * + * @param string $route Optional route to the page. + * @param string|null $lang Optional language code for multilingual links. + * @param bool|null $absolute If true, return absolute url, if false, return relative url. Otherwise return default. + * @return string + */ + public function url($route = '/', $lang = null, $absolute = null) + { + if (!$route || $route === '/') { + return $this->homeUrl($lang, $absolute); + } + + return $this->baseUrl($lang, $absolute) . Uri::filterPath($route); + } + + /** + * @param string $method + * @return void + */ + public function setCheckMethod($method): void + { + $this->check_method = strtolower($method); + } + + /** + * @return void + */ + public function register(): void + { + $config = $this->grav['config']; + $type = $config->get('system.pages.type'); + if ($type === 'flex') { + $this->initFlexPages(); + } + } + + /** + * Reset pages (used in search indexing etc). + * + * @return void + */ + public function reset(): void + { + $this->initialized = false; + + $this->init(); + } + + /** + * Class initialization. Must be called before using this class. + */ + public function init(): void + { + if ($this->initialized) { + return; + } + + $config = $this->grav['config']; + $this->ignore_files = (array)$config->get('system.pages.ignore_files'); + $this->ignore_folders = (array)$config->get('system.pages.ignore_folders'); + $this->ignore_hidden = (bool)$config->get('system.pages.ignore_hidden'); + $this->fire_events = (bool)$config->get('system.pages.events.page'); + + $this->instances = []; + $this->index = []; + $this->children = []; + $this->routes = []; + + if (!$this->check_method) { + $this->setCheckMethod($config->get('system.cache.check.method', 'file')); + } + + if ($this->enable_pages === false) { + $page = $this->buildRootPage(); + $this->instances[$page->path()] = $page; + + return; + } + + $this->buildPages(); + + $this->initialized = true; + } + + /** + * Get or set last modification time. + * + * @param int|null $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 PageInterface[] + */ + public function instances() + { + $instances = []; + foreach ($this->index as $path => $instance) { + $page = $this->get($path); + if ($page) { + $instances[$path] = $page; + } + } + + return $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 PageInterface $page Page to be added. + * @param string|null $route Optional route (uses route from the object if not set). + */ + public function addPage(PageInterface $page, $route = null): void + { + $path = $page->path() ?? ''; + if (!isset($this->index[$path])) { + $this->index[$path] = $page; + $this->instances[$path] = $page; + } + $route = $page->route($route); + $parent = $page->parent(); + if ($parent) { + $this->children[$parent->path() ?? ''][$path] = ['slug' => $page->slug()]; + } + $this->routes[$route] = $path; + + $this->grav->fireEvent('onPageProcessed', new Event(['page' => $page])); + } + + /** + * Get a collection of pages in the given context. + * + * @param array $params + * @param array $context + * @return PageCollectionInterface|Collection + */ + public function getCollection(array $params = [], array $context = []) + { + if (!isset($params['items'])) { + return new Collection(); + } + + /** @var Config $config */ + $config = $this->grav['config']; + + $context += [ + 'event' => true, + 'pagination' => true, + 'url_taxonomy_filters' => $config->get('system.pages.url_taxonomy_filters'), + 'taxonomies' => (array)$config->get('site.taxonomies'), + 'pagination_page' => 1, + 'self' => null, + ]; + + // Include taxonomies from the URL if requested. + $process_taxonomy = $params['url_taxonomy_filters'] ?? $context['url_taxonomy_filters']; + if ($process_taxonomy) { + /** @var Uri $uri */ + $uri = $this->grav['uri']; + foreach ($context['taxonomies'] as $taxonomy) { + $param = $uri->param(rawurlencode($taxonomy)); + $items = is_string($param) ? explode(',', $param) : []; + foreach ($items as $item) { + $params['taxonomies'][$taxonomy][] = htmlspecialchars_decode(rawurldecode($item), ENT_QUOTES); + } + } + } + + $pagination = $params['pagination'] ?? $context['pagination']; + if ($pagination && !isset($params['page'], $params['start'])) { + /** @var Uri $uri */ + $uri = $this->grav['uri']; + $context['current_page'] = $uri->currentPage(); + } + + $collection = $this->evaluate($params['items'], $context['self']); + $collection->setParams($params); + + // Filter by taxonomies. + foreach ($params['taxonomies'] ?? [] as $taxonomy => $items) { + foreach ($collection as $page) { + // Don't include modules + if ($page->isModule()) { + continue; + } + + $test = $page->taxonomy()[$taxonomy] ?? []; + foreach ($items as $item) { + if (!$test || !in_array($item, $test, true)) { + $collection->remove($page->path()); + } + } + } + } + + $filters = $params['filter'] ?? []; + + // Assume published=true if not set. + if (!isset($filters['published']) && !isset($filters['non-published'])) { + $filters['published'] = true; + } + + // Remove any inclusive sets from filter. + $sets = ['published', 'visible', 'modular', 'routable']; + foreach ($sets as $type) { + $nonType = "non-{$type}"; + if (isset($filters[$type], $filters[$nonType]) && $filters[$type] === $filters[$nonType]) { + if (!$filters[$type]) { + // Both options are false, return empty collection as nothing can match the filters. + return new Collection(); + } + + // Both options are true, remove opposite filters as all pages will match the filters. + unset($filters[$type], $filters[$nonType]); + } + } + + // Filter the collection + foreach ($filters as $type => $filter) { + if (null === $filter) { + continue; + } + + // Convert non-type to type. + if (str_starts_with($type, 'non-')) { + $type = substr($type, 4); + $filter = !$filter; + } + + switch ($type) { + case 'translated': + if ($filter) { + $collection = $collection->translated(); + } else { + $collection = $collection->nonTranslated(); + } + break; + case 'published': + if ($filter) { + $collection = $collection->published(); + } else { + $collection = $collection->nonPublished(); + } + break; + case 'visible': + if ($filter) { + $collection = $collection->visible(); + } else { + $collection = $collection->nonVisible(); + } + break; + case 'page': + if ($filter) { + $collection = $collection->pages(); + } else { + $collection = $collection->modules(); + } + break; + case 'module': + case 'modular': + if ($filter) { + $collection = $collection->modules(); + } else { + $collection = $collection->pages(); + } + break; + case 'routable': + if ($filter) { + $collection = $collection->routable(); + } else { + $collection = $collection->nonRoutable(); + } + break; + case 'type': + $collection = $collection->ofType($filter); + break; + case 'types': + $collection = $collection->ofOneOfTheseTypes($filter); + break; + case 'access': + $collection = $collection->ofOneOfTheseAccessLevels($filter); + break; + } + } + + if (isset($params['dateRange'])) { + $start = $params['dateRange']['start'] ?? null; + $end = $params['dateRange']['end'] ?? null; + $field = $params['dateRange']['field'] ?? null; + $collection = $collection->dateRange($start, $end, $field); + } + + if (isset($params['order'])) { + $by = $params['order']['by'] ?? 'default'; + $dir = $params['order']['dir'] ?? 'asc'; + $custom = $params['order']['custom'] ?? null; + $sort_flags = $params['order']['sort_flags'] ?? null; + + if (is_array($sort_flags)) { + $sort_flags = array_map('constant', $sort_flags); //transform strings to constant value + $sort_flags = array_reduce($sort_flags, static function ($a, $b) { + return $a | $b; + }, 0); //merge constant values using bit or + } + + $collection = $collection->order($by, $dir, $custom, $sort_flags); + } + + // New Custom event to handle things like pagination. + if ($context['event']) { + $this->grav->fireEvent('onCollectionProcessed', new Event(['collection' => $collection, 'context' => $context])); + } + + if ($context['pagination']) { + // Slice and dice the collection if pagination is required + $params = $collection->params(); + + $limit = (int)($params['limit'] ?? 0); + $page = (int)($params['page'] ?? $context['current_page'] ?? 0); + $start = (int)($params['start'] ?? 0); + $start = $limit > 0 && $page > 0 ? ($page - 1) * $limit : max(0, $start); + + if ($start || ($limit && $collection->count() > $limit)) { + $collection->slice($start, $limit ?: null); + } + } + + return $collection; + } + + /** + * @param array|string $value + * @param PageInterface|null $self + * @return Collection + */ + protected function evaluate($value, PageInterface $self = null) + { + // Parse command. + if (is_string($value)) { + // Format: @command.param + $cmd = $value; + $params = []; + } elseif (is_array($value) && count($value) === 1 && !is_int(key($value))) { + // Format: @command.param: { attr1: value1, attr2: value2 } + $cmd = (string)key($value); + $params = (array)current($value); + } else { + $result = []; + foreach ((array)$value as $key => $val) { + if (is_int($key)) { + $result = $result + $this->evaluate($val, $self)->toArray(); + } else { + $result = $result + $this->evaluate([$key => $val], $self)->toArray(); + } + } + + return new Collection($result); + } + + $parts = explode('.', $cmd); + $scope = array_shift($parts); + $type = $parts[0] ?? null; + + /** @var PageInterface|null $page */ + $page = null; + switch ($scope) { + case 'self@': + case '@self': + $page = $self; + break; + + case 'page@': + case '@page': + $page = isset($params[0]) ? $this->find($params[0]) : null; + break; + + case 'root@': + case '@root': + $page = $this->root(); + break; + + case 'taxonomy@': + 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 = Grav::instance()['taxonomy']; + + if (!empty($parts)) { + $params = [implode('.', $parts) => $params]; + } + + return $taxonomy_map->findTaxonomy($params); + } + + if (!$page) { + return new Collection(); + } + + // Handle '@page', '@page.modular: false', '@self' and '@self.modular: false'. + if (null === $type || (in_array($type, ['modular', 'modules']) && ($params[0] ?? null) === false)) { + $type = 'children'; + } + + switch ($type) { + case 'all': + $collection = $page->children(); + break; + case 'modules': + case 'modular': + $collection = $page->children()->modules(); + break; + case 'pages': + case 'children': + $collection = $page->children()->pages(); + break; + case 'page': + case 'self': + $collection = !$page->root() ? (new Collection())->addPage($page) : new Collection(); + break; + case 'parent': + $parent = $page->parent(); + $collection = new Collection(); + $collection = $parent ? $collection->addPage($parent) : $collection; + break; + case 'siblings': + $parent = $page->parent(); + if ($parent) { + /** @var Collection $collection */ + $collection = $parent->children(); + $collection = $collection->remove($page->path()); + } else { + $collection = new Collection(); + } + break; + case 'descendants': + $collection = $this->all($page)->remove($page->path())->pages(); + break; + default: + // Unknown type; return empty collection. + $collection = new Collection(); + break; + } + + return $collection; + } + + /** + * Sort sub-pages in a page. + * + * @param PageInterface $page + * @param string|null $order_by + * @param string|null $order_dir + * @return array + */ + public function sort(PageInterface $page, $order_by = null, $order_dir = null, $sort_flags = null) + { + if ($order_by === null) { + $order_by = $page->orderBy(); + } + if ($order_dir === null) { + $order_dir = $page->orderDir(); + } + + $path = $page->path(); + if (null === $path) { + return []; + } + + $children = $this->children[$path] ?? []; + + if (!$children) { + return $children; + } + + if (!isset($this->sort[$path][$order_by])) { + $this->buildSort($path, $children, $order_by, $page->orderManual(), $sort_flags); + } + + $sort = $this->sort[$path][$order_by]; + + if ($order_dir !== 'asc') { + $sort = array_reverse($sort); + } + + return $sort; + } + + /** + * @param Collection $collection + * @param string $orderBy + * @param string $orderDir + * @param array|null $orderManual + * @param int|null $sort_flags + * @return array + * @internal + */ + public function sortCollection(Collection $collection, $orderBy, $orderDir = 'asc', $orderManual = null, $sort_flags = null) + { + $items = $collection->toArray(); + if (!$items) { + return []; + } + + $lookup = md5(json_encode($items) . json_encode($orderManual) . $orderBy . $orderDir); + if (!isset($this->sort[$lookup][$orderBy])) { + $this->buildSort($lookup, $items, $orderBy, $orderManual, $sort_flags); + } + + $sort = $this->sort[$lookup][$orderBy]; + + if ($orderDir !== 'asc') { + $sort = array_reverse($sort); + } + + return $sort; + } + + /** + * Get a page instance. + * + * @param string $path The filesystem full path of the page + * @return PageInterface|null + * @throws RuntimeException + */ + public function get($path) + { + $path = (string)$path; + if ($path === '') { + return null; + } + + // Check for local instances first. + if (array_key_exists($path, $this->instances)) { + return $this->instances[$path]; + } + + $instance = $this->index[$path] ?? null; + if (is_string($instance)) { + if ($this->directory) { + /** @var Language $language */ + $language = $this->grav['language']; + $lang = $language->getActive(); + if ($lang) { + $languages = $language->getFallbackLanguages($lang, true); + $key = $instance; + $instance = null; + foreach ($languages as $code) { + $test = $code ? $key . ':' . $code : $key; + if (($instance = $this->directory->getObject($test, 'flex_key')) !== null) { + break; + } + } + } else { + $instance = $this->directory->getObject($instance, 'flex_key'); + } + } + + if ($instance instanceof PageInterface) { + if ($this->fire_events && method_exists($instance, 'initialize')) { + $instance->initialize(); + } + } else { + /** @var Debugger $debugger */ + $debugger = $this->grav['debugger']; + $debugger->addMessage(sprintf('Flex page %s is missing or broken!', $instance), 'debug'); + } + } + + if ($instance) { + $this->instances[$path] = $instance; + } + + return $instance; + } + + /** + * Get children of the path. + * + * @param string $path + * @return Collection + */ + public function children($path) + { + $children = $this->children[(string)$path] ?? []; + + return new Collection($children, [], $this); + } + + /** + * Get a page ancestor. + * + * @param string $route The relative URL of the page + * @param string|null $path The relative path of the ancestor folder + * @return PageInterface|null + */ + public function ancestor($route, $path = null) + { + if ($path !== null) { + $page = $this->find($route, true); + + if ($page && $page->path() === $path) { + return $page; + } + + $parent = $page ? $page->parent() : null; + if ($parent && !$parent->root()) { + return $this->ancestor($parent->route(), $path); + } + } + + return null; + } + + /** + * Get a page ancestor trait. + * + * @param string $route The relative route of the page + * @param string|null $field The field name of the ancestor to query for + * @return PageInterface|null + */ + public function inherited($route, $field = null) + { + if ($field !== null) { + $page = $this->find($route, true); + + $parent = $page ? $page->parent() : null; + if ($parent && $parent->value('header.' . $field) !== null) { + return $parent; + } + if ($parent && !$parent->root()) { + return $this->inherited($parent->route(), $field); + } + } + + return null; + } + + /** + * Find a page based on route. + * + * @param string $route The route of the page + * @param bool $all If true, return also non-routable pages, otherwise return null if page isn't routable + * @return PageInterface|null + */ + public function find($route, $all = false) + { + $route = urldecode((string)$route); + + // Fetch page if there's a defined route to it. + $path = $this->routes[$route] ?? null; + $page = null !== $path ? $this->get($path) : null; + + // Try without trailing slash + if (null === $page && Utils::endsWith($route, '/')) { + $path = $this->routes[rtrim($route, '/')] ?? null; + $page = null !== $path ? $this->get($path) : null; + } + + if (!$all && !isset($this->grav['admin'])) { + if (null === $page || !$page->routable()) { + // If the page cannot be accessed, look for the site wide routes and wildcards. + $page = $this->findSiteBasedRoute($route) ?? $page; + } + } + + return $page; + } + + /** + * Check site based routes. + * + * @param string $route + * @return PageInterface|null + */ + protected function findSiteBasedRoute($route) + { + /** @var Config $config */ + $config = $this->grav['config']; + + $site_routes = $config->get('site.routes'); + if (!is_array($site_routes)) { + return null; + } + + $page = null; + + // See if route matches one in the site configuration + $site_route = $site_routes[$route] ?? null; + if ($site_route) { + $page = $this->find($site_route); + } else { + // Use reverse order because of B/C (previously matched multiple and returned the last match). + foreach (array_reverse($site_routes, true) as $pattern => $replace) { + $pattern = '#^' . str_replace('/', '\/', ltrim($pattern, '^')) . '#'; + try { + $found = preg_replace($pattern, $replace, $route); + if ($found && $found !== $route) { + $page = $this->find($found); + if ($page) { + return $page; + } + } + } catch (ErrorException $e) { + $this->grav['log']->error('site.routes: ' . $pattern . '-> ' . $e->getMessage()); + } + } + } + + return $page; + } + + /** + * Dispatch URI to a page. + * + * @param string $route The relative URL of the page + * @param bool $all If true, return also non-routable pages, otherwise return null if page isn't routable + * @param bool $redirect If true, allow redirects + * @return PageInterface|null + * @throws Exception + */ + public function dispatch($route, $all = false, $redirect = true) + { + $page = $this->find($route, true); + + // If we want all pages or are in admin, return what we already have. + if ($all || isset($this->grav['admin'])) { + return $page; + } + + if ($page) { + $routable = $page->routable(); + if ($redirect) { + if ($page->redirect()) { + // Follow a redirect page. + $this->grav->redirectLangSafe($page->redirect()); + } + + if (!$routable) { + /** @var Collection $children */ + $children = $page->children()->visible()->routable()->published(); + $child = $children->first(); + if ($child !== null) { + // Redirect to the first visible child as current page isn't routable. + $this->grav->redirectLangSafe($child->route()); + } + } + } + + if ($routable) { + return $page; + } + } + + $route = urldecode((string)$route); + + // The page cannot be reached, look into site wide redirects, routes and wildcards. + $redirectedPage = $this->findSiteBasedRoute($route); + if ($redirectedPage) { + $page = $this->dispatch($redirectedPage->route(), false, $redirect); + } + + /** @var Config $config */ + $config = $this->grav['config']; + + /** @var Uri $uri */ + $uri = $this->grav['uri']; + /** @var \Grav\Framework\Uri\Uri $source_url */ + $source_url = $uri->uri(false); + + // Try Regex style redirects + $site_redirects = $config->get('site.redirects'); + if (is_array($site_redirects)) { + foreach ((array)$site_redirects as $pattern => $replace) { + $pattern = ltrim($pattern, '^'); + $pattern = '#^' . str_replace('/', '\/', $pattern) . '#'; + try { + /** @var string $found */ + $found = preg_replace($pattern, $replace, $source_url); + if ($found && $found !== $source_url) { + $this->grav->redirectLangSafe($found); + } + } catch (ErrorException $e) { + $this->grav['log']->error('site.redirects: ' . $pattern . '-> ' . $e->getMessage()); + } + } + } + + return $page; + } + + /** + * Get root page. + * + * @return PageInterface + * @throws RuntimeException + */ + public function root() + { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + + $path = $locator->findResource('page://'); + $root = is_string($path) ? $this->get(rtrim($path, '/')) : null; + if (null === $root) { + throw new RuntimeException('Internal error'); + } + + return $root; + } + + /** + * Get a blueprint for a page type. + * + * @param string $type + * @return Blueprint + */ + public function blueprints($type) + { + if ($this->blueprints === null) { + $this->blueprints = new Blueprints(self::getTypes()); + } + + try { + $blueprint = $this->blueprints->get($type); + } catch (RuntimeException $e) { + $blueprint = $this->blueprints->get('default'); + } + + if (empty($blueprint->initialized)) { + $blueprint->initialized = true; + $this->grav->fireEvent('onBlueprintCreated', new Event(['blueprint' => $blueprint, 'type' => $type])); + } + + return $blueprint; + } + + /** + * Get all pages + * + * @param PageInterface|null $current + * @return Collection + */ + public function all(PageInterface $current = null) + { + $all = new Collection(); + + /** @var PageInterface $current */ + $current = $current ?: $this->root(); + + if (!$current->root()) { + $all[$current->path()] = ['slug' => $current->slug()]; + } + + foreach ($current->children() as $next) { + $all->append($this->all($next)); + } + + return $all; + } + + /** + * Get available parents raw routes. + * + * @return array + */ + public static function parentsRawRoutes() + { + $rawRoutes = true; + + return self::getParents($rawRoutes); + } + + /** + * Get available parents routes + * + * @param bool $rawRoutes get the raw route or the normal route + * @return array + */ + private static function getParents($rawRoutes) + { + $grav = Grav::instance(); + + /** @var Pages $pages */ + $pages = $grav['pages']; + + $parents = $pages->getList(null, 0, $rawRoutes); + + if (isset($grav['admin'])) { + // Remove current route from parents + + /** @var Admin $admin */ + $admin = $grav['admin']; + + $page = $admin->getPage($admin->route); + $page_route = $page->route(); + if (isset($parents[$page_route])) { + unset($parents[$page_route]); + } + } + + return $parents; + } + + /** + * Get list of route/title of all pages. Title is in HTML. + * + * @param PageInterface|null $current + * @param int $level + * @param bool $rawRoutes + * @param bool $showAll + * @param bool $showFullpath + * @param bool $showSlug + * @param bool $showModular + * @param bool $limitLevels + * @return array + */ + public function getList(PageInterface $current = null, $level = 0, $rawRoutes = false, $showAll = true, $showFullpath = false, $showSlug = false, $showModular = false, $limitLevels = false) + { + if (!$current) { + if ($level) { + throw new RuntimeException('Internal error'); + } + + $current = $this->root(); + } + + $list = []; + + if (!$current->root()) { + if ($rawRoutes) { + $route = $current->rawRoute(); + } else { + $route = $current->route(); + } + + if ($showFullpath) { + $option = htmlspecialchars($current->route()); + } else { + $extra = $showSlug ? '(' . $current->slug() . ') ' : ''; + $option = str_repeat('—-', $level). '▸ ' . $extra . htmlspecialchars($current->title()); + } + + $list[$route] = $option; + } + + if ($limitLevels === false || ($level+1 < $limitLevels)) { + foreach ($current->children() as $next) { + if ($showAll || $next->routable() || ($next->isModule() && $showModular)) { + $list = array_merge($list, $this->getList($next, $level + 1, $rawRoutes, $showAll, $showFullpath, $showSlug, $showModular, $limitLevels)); + } + } + } + + return $list; + } + + /** + * Get available page types. + * + * @return Types + */ + public static function getTypes() + { + if (null === self::$types) { + $grav = Grav::instance(); + + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + + // Prevent calls made before theme:// has been initialized (happens when upgrading old version of Admin plugin). + if (!$locator->isStream('theme://')) { + return new Types(); + } + + $scanBlueprintsAndTemplates = static function (Types $types) use ($grav) { + // Scan blueprints + $event = new TypesEvent(); + $event->types = $types; + $grav->fireEvent('onGetPageBlueprints', $event); + + $types->init(); + + // Try new location first. + $lookup = 'theme://blueprints/pages/'; + if (!is_dir($lookup)) { + $lookup = 'theme://blueprints/'; + } + $types->scanBlueprints($lookup); + + // Scan templates + $event = new TypesEvent(); + $event->types = $types; + $grav->fireEvent('onGetPageTemplates', $event); + + $types->scanTemplates('theme://templates/'); + }; + + if ($grav['config']->get('system.cache.enabled')) { + /** @var Cache $cache */ + $cache = $grav['cache']; + + // Use cached types if possible. + $types_cache_id = md5('types'); + $types = $cache->fetch($types_cache_id); + + if (!$types instanceof Types) { + $types = new Types(); + $scanBlueprintsAndTemplates($types); + $cache->save($types_cache_id, $types); + } + } else { + $types = new Types(); + $scanBlueprintsAndTemplates($types); + } + + // Register custom paths to the locator. + $locator = $grav['locator']; + foreach ($types as $type => $paths) { + foreach ($paths as $k => $path) { + if (strpos($path, 'blueprints://') === 0) { + unset($paths[$k]); + } + } + if ($paths) { + $locator->addPath('blueprints', "pages/$type.yaml", $paths); + } + } + + self::$types = $types; + } + + return self::$types; + } + + /** + * Get available page types. + * + * @return array + */ + public static function types() + { + $types = self::getTypes(); + + return $types->pageSelect(); + } + + /** + * Get available page types. + * + * @return array + */ + public static function modularTypes() + { + $types = self::getTypes(); + + return $types->modularSelect(); + } + + /** + * Get template types based on page type (standard or modular) + * + * @param string|null $type + * @return array + */ + public static function pageTypes($type = null) + { + if (null === $type && isset(Grav::instance()['admin'])) { + /** @var Admin $admin */ + $admin = Grav::instance()['admin']; + + /** @var PageInterface|null $page */ + $page = $admin->page(); + + $type = $page && $page->isModule() ? 'modular' : 'standard'; + } + + switch ($type) { + case 'standard': + return static::types(); + case 'modular': + return static::modularTypes(); + } + + return []; + } + + /** + * Get access levels of the site pages + * + * @return array + */ + public function accessLevels() + { + $accessLevels = []; + foreach ($this->all() as $page) { + if ($page instanceof PageInterface && isset($page->header()->access)) { + if (is_array($page->header()->access)) { + foreach ($page->header()->access as $index => $accessLevel) { + if (is_array($accessLevel)) { + foreach ($accessLevel as $innerIndex => $innerAccessLevel) { + $accessLevels[] = $innerIndex; + } + } else { + $accessLevels[] = $index; + } + } + } else { + $accessLevels[] = $page->header()->access; + } + } + } + + return array_unique($accessLevels); + } + + /** + * Get available parents routes + * + * @return array + */ + public static function parents() + { + $rawRoutes = false; + + return self::getParents($rawRoutes); + } + + /** + * Gets the home route + * + * @return string + */ + public static function getHomeRoute() + { + if (empty(self::$home_route)) { + $grav = Grav::instance(); + + /** @var Config $config */ + $config = $grav['config']; + + /** @var Language $language */ + $language = $grav['language']; + + $home = $config->get('system.home.alias'); + + if ($language->enabled()) { + $home_aliases = $config->get('system.home.aliases'); + if ($home_aliases) { + $active = $language->getActive(); + $default = $language->getDefault(); + + try { + if ($active) { + $home = $home_aliases[$active]; + } else { + $home = $home_aliases[$default]; + } + } catch (ErrorException $e) { + $home = $home_aliases[$default]; + } + } + } + + self::$home_route = trim($home, '/'); + } + + return self::$home_route; + } + + /** + * Needed for testing where we change the home route via config + * + * @return string|null + */ + public static function resetHomeRoute() + { + self::$home_route = null; + + return self::getHomeRoute(); + } + + protected function initFlexPages(): void + { + /** @var Debugger $debugger */ + $debugger = $this->grav['debugger']; + $debugger->addMessage('Pages: Flex Directory'); + + /** @var Flex $flex */ + $flex = $this->grav['flex']; + $directory = $flex->getDirectory('pages'); + + /** @var EventDispatcher $dispatcher */ + $dispatcher = $this->grav['events']; + + // Stop /admin/pages from working, display error instead. + $dispatcher->addListener( + 'onAdminPage', + static function (Event $event) use ($directory) { + $grav = Grav::instance(); + $admin = $grav['admin']; + [$base,$location,] = $admin->getRouteDetails(); + if ($location !== 'pages' || isset($grav['flex_objects'])) { + return; + } + + /** @var PageInterface $page */ + $page = $event['page']; + $page->init(new SplFileInfo('plugin://admin/pages/admin/error.md')); + $page->routable(true); + $header = $page->header(); + $header->title = 'Please install missing plugin'; + $page->content("## Please install and enable **[Flex Objects]({$base}/plugins/flex-objects)** plugin. It is required to edit **Flex Pages**."); + + /** @var Header $header */ + $header = $page->header(); + $menu = $directory->getConfig('admin.menu.list'); + $header->access = $menu['authorize'] ?? ['admin.super']; + }, + 100000 + ); + + $this->directory = $directory; + } + + /** + * Builds pages. + * + * @internal + */ + protected function buildPages(): void + { + /** @var Debugger $debugger */ + $debugger = $this->grav['debugger']; + $debugger->startTimer('build-pages', 'Init frontend routes'); + + if ($this->directory) { + $this->buildFlexPages($this->directory); + } else { + $this->buildRegularPages(); + } + $debugger->stopTimer('build-pages'); + } + + protected function buildFlexPages(FlexDirectory $directory): void + { + /** @var Config $config */ + $config = $this->grav['config']; + + // TODO: right now we are just emulating normal pages, it is inefficient and bad... but works! + /** @var PageCollection|PageIndex $collection */ + $collection = $directory->getIndex(null, 'storage_key'); + $cache = $directory->getCache('index'); + + /** @var Language $language */ + $language = $this->grav['language']; + + $this->pages_cache_id = 'pages-' . md5($collection->getCacheChecksum() . $language->getActive() . $config->checksum()); + + $cached = $cache->get($this->pages_cache_id); + + if ($cached && $this->getVersion() === $cached[0]) { + [, $this->index, $this->routes, $this->children, $taxonomy_map, $this->sort] = $cached; + + /** @var Taxonomy $taxonomy */ + $taxonomy = $this->grav['taxonomy']; + $taxonomy->taxonomy($taxonomy_map); + + return; + } + + /** @var Debugger $debugger */ + $debugger = $this->grav['debugger']; + $debugger->addMessage('Page cache missed, rebuilding Flex Pages..'); + + $root = $collection->getRoot(); + $root_path = $root->path(); + $this->routes = []; + $this->instances = [$root_path => $root]; + $this->index = [$root_path => $root]; + $this->children = []; + $this->sort = []; + + if ($this->fire_events) { + $this->grav->fireEvent('onBuildPagesInitialized'); + } + + /** @var PageInterface $page */ + foreach ($collection as $page) { + $path = $page->path(); + if (null === $path) { + throw new RuntimeException('Internal error'); + } + + if ($page instanceof FlexTranslateInterface) { + $page = $page->hasTranslation() ? $page->getTranslation() : null; + } + + if (!$page instanceof FlexPageObject || $path === $root_path) { + continue; + } + + if ($this->fire_events) { + if (method_exists($page, 'initialize')) { + $page->initialize(); + } else { + // TODO: Deprecated, only used in 1.7 betas. + $this->grav->fireEvent('onPageProcessed', new Event(['page' => $page])); + } + } + + $parent = dirname($path); + + $route = $page->rawRoute(); + + // Skip duplicated empty folders (git revert does not remove those). + // TODO: still not perfect, will only work if the page has been translated. + if (isset($this->routes[$route])) { + $oldPath = $this->routes[$route]; + if ($page->isPage()) { + unset($this->index[$oldPath], $this->children[dirname($oldPath)][$oldPath]); + } else { + continue; + } + } + + $this->routes[$route] = $path; + $this->instances[$path] = $page; + $this->index[$path] = $page->getFlexKey(); + // FIXME: ... better... + $this->children[$parent][$path] = ['slug' => $page->slug()]; + if (!isset($this->children[$path])) { + $this->children[$path] = []; + } + } + + foreach ($this->children as $path => $list) { + $page = $this->instances[$path] ?? null; + if (null === $page) { + continue; + } + // Call onFolderProcessed event. + if ($this->fire_events) { + $this->grav->fireEvent('onFolderProcessed', new Event(['page' => $page])); + } + // Sort the children. + $this->children[$path] = $this->sort($page); + } + + $this->routes = []; + $this->buildRoutes(); + + // cache if needed + if (null !== $cache) { + /** @var Taxonomy $taxonomy */ + $taxonomy = $this->grav['taxonomy']; + $taxonomy_map = $taxonomy->taxonomy(); + + // save pages, routes, taxonomy, and sort to cache + $cache->set($this->pages_cache_id, [$this->getVersion(), $this->index, $this->routes, $this->children, $taxonomy_map, $this->sort]); + } + } + + /** + * @return Page + */ + protected function buildRootPage() + { + $grav = Grav::instance(); + + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + $path = $locator->findResource('page://'); + if (!is_string($path)) { + throw new RuntimeException('Internal Error'); + } + + /** @var Config $config */ + $config = $grav['config']; + + $page = new Page(); + $page->path($path); + $page->orderDir($config->get('system.pages.order.dir')); + $page->orderBy($config->get('system.pages.order.by')); + $page->modified(0); + $page->routable(false); + $page->template('default'); + $page->extension('.md'); + + return $page; + } + + protected function buildRegularPages(): void + { + /** @var Config $config */ + $config = $this->grav['config']; + + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + + /** @var Language $language */ + $language = $this->grav['language']; + + $pages_dirs = $this->getPagesPaths(); + + // Set active language + $this->active_lang = $language->getActive(); + + if ($config->get('system.cache.enabled')) { + /** @var Language $language */ + $language = $this->grav['language']; + + // how should we check for last modified? Default is by file + switch ($this->check_method) { + case 'none': + case 'off': + $hash = 0; + break; + case 'folder': + $hash = Folder::lastModifiedFolder($pages_dirs); + break; + case 'hash': + $hash = Folder::hashAllFiles($pages_dirs); + break; + default: + $hash = Folder::lastModifiedFile($pages_dirs); + } + + $this->simple_pages_hash = json_encode($pages_dirs) . $hash . $config->checksum(); + $this->pages_cache_id = md5($this->simple_pages_hash . $language->getActive()); + + /** @var Cache $cache */ + $cache = $this->grav['cache']; + $cached = $cache->fetch($this->pages_cache_id); + if ($cached && $this->getVersion() === $cached[0]) { + [, $this->index, $this->routes, $this->children, $taxonomy_map, $this->sort] = $cached; + + /** @var Taxonomy $taxonomy */ + $taxonomy = $this->grav['taxonomy']; + $taxonomy->taxonomy($taxonomy_map); + + return; + } + + $this->grav['debugger']->addMessage('Page cache missed, rebuilding pages..'); + } else { + $this->grav['debugger']->addMessage('Page cache disabled, rebuilding pages..'); + } + + $this->resetPages($pages_dirs); + } + + protected function getPagesPaths(): array + { + $grav = Grav::instance(); + $locator = $grav['locator']; + $paths = []; + + $dirs = (array) $grav['config']->get('system.pages.dirs', ['page://']); + foreach ($dirs as $dir) { + $path = $locator->findResource($dir); + if (file_exists($path) && !in_array($path, $paths, true)) { + $paths[] = $path; + } + } + + return $paths; + } + + /** + * Accessible method to manually reset the pages cache + * + * @param array $pages_dirs + */ + public function resetPages(array $pages_dirs): void + { + $this->sort = []; + + foreach ($pages_dirs as $dir) { + $this->recurse($dir); + } + + $this->buildRoutes(); + + // cache if needed + if ($this->grav['config']->get('system.cache.enabled')) { + /** @var Cache $cache */ + $cache = $this->grav['cache']; + /** @var Taxonomy $taxonomy */ + $taxonomy = $this->grav['taxonomy']; + + // save pages, routes, taxonomy, and sort to cache + $cache->save($this->pages_cache_id, [$this->getVersion(), $this->index, $this->routes, $this->children, $taxonomy->taxonomy(), $this->sort]); + } + } + + /** + * Recursive function to load & build page relationships. + * + * @param string $directory + * @param PageInterface|null $parent + * @return PageInterface + * @throws RuntimeException + * @internal + */ + protected function recurse(string $directory, PageInterface $parent = null) + { + $directory = rtrim($directory, DS); + $page = new Page; + + /** @var Config $config */ + $config = $this->grav['config']; + + /** @var Language $language */ + $language = $this->grav['language']; + + // Stuff to do at root page + // Fire event for memory and time consuming plugins... + if ($parent === null && $this->fire_events) { + $this->grav->fireEvent('onBuildPagesInitialized'); + } + + $page->path($directory); + if ($parent) { + $page->parent($parent); + } + + $page->orderDir($config->get('system.pages.order.dir')); + $page->orderBy($config->get('system.pages.order.by')); + + // Add into instances + if (!isset($this->index[$page->path()])) { + $this->index[$page->path()] = $page; + $this->instances[$page->path()] = $page; + if ($parent && $page->path()) { + $this->children[$parent->path()][$page->path()] = ['slug' => $page->slug()]; + } + } elseif ($parent !== null) { + throw new RuntimeException('Fatal error when creating page instances.'); + } + + // Build regular expression for all the allowed page extensions. + $page_extensions = $language->getFallbackPageExtensions(); + $regex = '/^[^\.]*(' . implode('|', array_map( + static function ($str) { + return preg_quote($str, '/'); + }, + $page_extensions + )) . ')$/'; + + $folders = []; + $page_found = null; + $page_extension = '.md'; + $last_modified = 0; + + $iterator = new FilesystemIterator($directory); + foreach ($iterator as $file) { + $filename = $file->getFilename(); + + // Ignore all hidden files if set. + if ($this->ignore_hidden && $filename && strpos($filename, '.') === 0) { + continue; + } + + // Handle folders later. + if ($file->isDir()) { + // But ignore all folders in ignore list. + if (!in_array($filename, $this->ignore_folders, true)) { + $folders[] = $file; + } + continue; + } + + // Ignore all files in ignore list. + if (in_array($filename, $this->ignore_files, true)) { + continue; + } + + // Update last modified date to match the last updated file in the folder. + $modified = $file->getMTime(); + if ($modified > $last_modified) { + $last_modified = $modified; + } + + // Page is the one that matches to $page_extensions list with the lowest index number. + if (preg_match($regex, $filename, $matches, PREG_OFFSET_CAPTURE)) { + $ext = $matches[1][0]; + + if ($page_found === null || array_search($ext, $page_extensions, true) < array_search($page_extension, $page_extensions, true)) { + $page_found = $file; + $page_extension = $ext; + } + } + } + + $content_exists = false; + if ($parent && $page_found) { + $page->init($page_found, $page_extension); + + $content_exists = true; + + if ($this->fire_events) { + $this->grav->fireEvent('onPageProcessed', new Event(['page' => $page])); + } + } + + // Now handle all the folders under the page. + /** @var FilesystemIterator $file */ + foreach ($folders as $file) { + $filename = $file->getFilename(); + + // if folder contains separator, continue + if (Utils::contains($file->getFilename(), $config->get('system.param_sep', ':'))) { + continue; + } + + if (!$page->path()) { + $page->path($file->getPath()); + } + + $path = $directory . DS . $filename; + $child = $this->recurse($path, $page); + + if (preg_match('/^(\d+\.)_/', $filename)) { + $child->routable(false); + $child->modularTwig(true); + } + + $this->children[$page->path()][$child->path()] = ['slug' => $child->slug()]; + + if ($this->fire_events) { + $this->grav->fireEvent('onFolderProcessed', new Event(['page' => $page])); + } + } + + if (!$content_exists) { + // Set routable to false if no page found + $page->routable(false); + + // Hide empty folders if option set + if ($config->get('system.pages.hide_empty_folders')) { + $page->visible(false); + } + } + + // Override the modified time if modular + if ($page->template() === 'modular') { + foreach ($page->collection() as $child) { + $modified = $child->modified(); + + if ($modified > $last_modified) { + $last_modified = $modified; + } + } + } + + // Override the modified and ID so that it takes the latest change into account + $page->modified($last_modified); + $page->id($last_modified . md5($page->filePath() ?? '')); + + // Sort based on Defaults or Page Overridden sort order + $this->children[$page->path()] = $this->sort($page); + + return $page; + } + + /** + * @internal + */ + protected function buildRoutes(): void + { + /** @var Taxonomy $taxonomy */ + $taxonomy = $this->grav['taxonomy']; + + // Get the home route + $home = self::resetHomeRoute(); + // Build routes and taxonomy map. + /** @var PageInterface|string $page */ + foreach ($this->index as $path => $page) { + if (is_string($page)) { + $page = $this->get($path); + } + + if (!$page || $page->root()) { + continue; + } + + // process taxonomy + $taxonomy->addTaxonomy($page); + + $page_path = $page->path(); + if (null === $page_path) { + throw new RuntimeException('Internal Error'); + } + + $route = $page->route(); + $raw_route = $page->rawRoute(); + + // add regular route + if ($route) { + if (isset($this->routes[$route]) && $this->routes[$route] !== $page_path) { + $this->grav['debugger']->addMessage("Route '{$route}' already exists: {$this->routes[$route]}, overwriting with {$page_path}"); + } + $this->routes[$route] = $page_path; + } + + // add raw route + if ($raw_route) { + if (isset($this->routes[$raw_route]) && $this->routes[$route] !== $page_path) { + $this->grav['debugger']->addMessage("Raw Route '{$raw_route}' already exists: {$this->routes[$raw_route]}, overwriting with {$page_path}"); + } + $this->routes[$raw_route] = $page_path; + } + + // add canonical route + $route_canonical = $page->routeCanonical(); + if ($route_canonical) { + if (isset($this->routes[$route_canonical]) && $this->routes[$route_canonical] !== $page_path) { + $this->grav['debugger']->addMessage("Canonical Route '{$route_canonical}' already exists: {$this->routes[$route_canonical]}, overwriting with {$page_path}"); + } + $this->routes[$route_canonical] = $page_path; + } + + // add aliases to routes list if they are provided + $route_aliases = $page->routeAliases(); + if ($route_aliases) { + foreach ($route_aliases as $alias) { + if (isset($this->routes[$alias]) && $this->routes[$alias] !== $page_path) { + $this->grav['debugger']->addMessage("Alias Route '{$alias}' already exists: {$this->routes[$alias]}, overwriting with {$page_path}"); + } + $this->routes[$alias] = $page_path; + } + } + } + + // Alias and set default route to home page. + $homeRoute = "/{$home}"; + if ($home && isset($this->routes[$homeRoute])) { + $home = $this->get($this->routes[$homeRoute]); + if ($home) { + $this->routes['/'] = $this->routes[$homeRoute]; + $home->route('/'); + } + } + } + + /** + * @param string $path + * @param array $pages + * @param string $order_by + * @param array|null $manual + * @param int|null $sort_flags + * @throws RuntimeException + * @internal + */ + protected function buildSort($path, array $pages, $order_by = 'default', $manual = null, $sort_flags = null): void + { + $list = []; + $header_query = null; + $header_default = null; + + // do this header query work only once + if (strpos($order_by, 'header.') === 0) { + $query = explode('|', str_replace('header.', '', $order_by), 2); + $header_query = array_shift($query) ?? ''; + $header_default = array_shift($query); + } + + foreach ($pages as $key => $info) { + $child = $this->get($key); + 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(); + $sort_flags = SORT_REGULAR; + break; + case 'modified': + $list[$key] = $child->modified(); + $sort_flags = SORT_REGULAR; + break; + case 'publish_date': + $list[$key] = $child->publishDate(); + $sort_flags = SORT_REGULAR; + break; + case 'unpublish_date': + $list[$key] = $child->unpublishDate(); + $sort_flags = SORT_REGULAR; + break; + case 'slug': + $list[$key] = $child->slug(); + break; + case 'basename': + $list[$key] = Utils::basename($key); + break; + case 'folder': + $list[$key] = $child->folder(); + break; + case 'manual': + case 'default': + default: + if (is_string($header_query)) { + $child_header = $child->header(); + if (!$child_header instanceof Header) { + $child_header = new Header((array)$child_header); + } + $header_value = $child_header->get($header_query); + if (is_array($header_value)) { + $list[$key] = implode(',', $header_value); + } elseif ($header_value) { + $list[$key] = $header_value; + } else { + $list[$key] = $header_default ?: $key; + } + $sort_flags = $sort_flags ?: SORT_REGULAR; + break; + } + $list[$key] = $key; + $sort_flags = $sort_flags ?: SORT_REGULAR; + } + } + + if (!$sort_flags) { + $sort_flags = SORT_NATURAL | SORT_FLAG_CASE; + } + + // handle special case when order_by is random + if ($order_by === 'random') { + $list = $this->arrayShuffle($list); + } else { + // else just sort the list according to specified key + if (extension_loaded('intl') && $this->grav['config']->get('system.intl_enabled')) { + $locale = setlocale(LC_COLLATE, '0'); //`setlocale` with a '0' param returns the current locale set + $col = Collator::create($locale); + if ($col) { + $col->setAttribute(Collator::NUMERIC_COLLATION, Collator::ON); + if (($sort_flags & SORT_NATURAL) === SORT_NATURAL) { + $list = preg_replace_callback('~([0-9]+)\.~', static function ($number) { + return sprintf('%032d.', $number[0]); + }, $list); + if (!is_array($list)) { + throw new RuntimeException('Internal Error'); + } + + $list_vals = array_values($list); + if (is_numeric(array_shift($list_vals))) { + $sort_flags = Collator::SORT_REGULAR; + } else { + $sort_flags = Collator::SORT_STRING; + } + } + + $col->asort($list, $sort_flags); + } else { + asort($list, $sort_flags); + } + } else { + asort($list, $sort_flags); + } + } + + + // Move manually ordered items into the beginning of the list. Order of the unlisted items does not change. + if (is_array($manual) && !empty($manual)) { + $new_list = []; + $i = count($manual); + + foreach ($list as $key => $dummy) { + $info = $pages[$key]; + $order = array_search($info['slug'], $manual, true); + if ($order === false) { + $order = $i++; + } + $new_list[$key] = (int)$order; + } + + $list = $new_list; + + // Apply manual ordering to the list. + asort($list, SORT_NUMERIC); + } + + foreach ($list as $key => $sort) { + $info = $pages[$key]; + $this->sort[$path][$order_by][$key] = $info; + } + } + + /** + * Shuffles an associative array + * + * @param array $list + * @return array + */ + protected function arrayShuffle(array $list): array + { + $keys = array_keys($list); + shuffle($keys); + + $new = []; + foreach ($keys as $key) { + $new[$key] = $list[$key]; + } + + return $new; + } + + /** + * @return string + */ + protected function getVersion(): string + { + return $this->directory ? 'flex' : 'regular'; + } + + /** + * Get the Pages cache ID + * + * this is particularly useful to know if pages have changed and you want + * to sync another cache with pages cache - works best in `onPagesInitialized()` + * + * @return null|string + */ + public function getPagesCacheId(): ?string + { + return $this->pages_cache_id; + } + + /** + * Get the simple pages hash that is not md5 encoded, and isn't specific to language + * + * @return null|string + */ + public function getSimplePagesHash(): ?string + { + return $this->simple_pages_hash; + } +} diff --git a/system/src/Grav/Common/Page/Traits/PageFormTrait.php b/system/src/Grav/Common/Page/Traits/PageFormTrait.php new file mode 100644 index 0000000..b99e7b7 --- /dev/null +++ b/system/src/Grav/Common/Page/Traits/PageFormTrait.php @@ -0,0 +1,126 @@ + blueprint, ...], where blueprint follows the regular form blueprint format. + * + * @return array + */ + public function getForms(): array + { + if (null === $this->_forms) { + $header = $this->header(); + + // Call event to allow filling the page header form dynamically (e.g. use case: Comments plugin) + $grav = Grav::instance(); + $grav->fireEvent('onFormPageHeaderProcessed', new Event(['page' => $this, 'header' => $header])); + + $rules = $header->rules ?? null; + if (!is_array($rules)) { + $rules = []; + } + + $forms = []; + + // First grab page.header.form + $form = $this->normalizeForm($header->form ?? null, null, $rules); + if ($form) { + $forms[$form['name']] = $form; + } + + // Append page.header.forms (override singular form if it clashes) + $headerForms = $header->forms ?? null; + if (is_array($headerForms)) { + foreach ($headerForms as $name => $form) { + $form = $this->normalizeForm($form, $name, $rules); + if ($form) { + $forms[$form['name']] = $form; + } + } + } + + $this->_forms = $forms; + } + + return $this->_forms; + } + + /** + * Add forms to this page. + * + * @param array $new + * @param bool $override + * @return $this + */ + public function addForms(array $new, $override = true) + { + // Initialize forms. + $this->forms(); + + foreach ($new as $name => $form) { + $form = $this->normalizeForm($form, $name); + $name = $form['name'] ?? null; + if ($name && ($override || !isset($this->_forms[$name]))) { + $this->_forms[$name] = $form; + } + } + + return $this; + } + + /** + * Alias of $this->getForms(); + * + * @return array + */ + public function forms(): array + { + return $this->getForms(); + } + + /** + * @param array|null $form + * @param string|null $name + * @param array $rules + * @return array|null + */ + protected function normalizeForm($form, $name = null, array $rules = []): ?array + { + if (!is_array($form)) { + return null; + } + + // Ignore numeric indexes on name. + if (!$name || (string)(int)$name === (string)$name) { + $name = null; + } + + $name = $name ?? $form['name'] ?? $this->slug(); + + $formRules = $form['rules'] ?? null; + if (!is_array($formRules)) { + $formRules = []; + } + + return ['name' => $name, 'rules' => $rules + $formRules] + $form; + } + + abstract public function header($var = null); + abstract public function slug($var = null); +} diff --git a/system/src/Grav/Common/Page/Types.php b/system/src/Grav/Common/Page/Types.php new file mode 100644 index 0000000..02fe99a --- /dev/null +++ b/system/src/Grav/Common/Page/Types.php @@ -0,0 +1,179 @@ +items[$type])) { + $this->items[$type] = []; + } elseif (null === $blueprint) { + return; + } + + if (null === $blueprint) { + $blueprint = $this->systemBlueprints[$type] ?? $this->systemBlueprints['default'] ?? null; + } + + if ($blueprint) { + array_unshift($this->items[$type], $blueprint); + } + } + + /** + * @return void + */ + public function init() + { + if (empty($this->systemBlueprints)) { + // Register all blueprints from the blueprints stream. + $this->systemBlueprints = $this->findBlueprints('blueprints://pages'); + foreach ($this->systemBlueprints as $type => $blueprint) { + $this->register($type); + } + } + } + + /** + * @param string $uri + * @return void + */ + public function scanBlueprints($uri) + { + if (!is_string($uri)) { + throw new InvalidArgumentException('First parameter must be URI'); + } + + foreach ($this->findBlueprints($uri) as $type => $blueprint) { + $this->register($type, $blueprint); + } + } + + /** + * @param string $uri + * @return void + */ + public function scanTemplates($uri) + { + if (!is_string($uri)) { + throw new InvalidArgumentException('First parameter must be URI'); + } + + $options = [ + 'compare' => 'Filename', + 'pattern' => '|\.html\.twig$|', + 'filters' => [ + 'value' => '|\.html\.twig$|' + ], + 'value' => 'Filename', + 'recursive' => false + ]; + + foreach (Folder::all($uri, $options) as $type) { + $this->register($type); + } + + $modular_uri = rtrim($uri, '/') . '/modular'; + if (is_dir($modular_uri)) { + foreach (Folder::all($modular_uri, $options) as $type) { + $this->register('modular/' . $type); + } + } + } + + /** + * @return array + */ + public function pageSelect() + { + $list = []; + foreach ($this->items as $name => $file) { + if (strpos($name, '/')) { + continue; + } + $list[$name] = ucfirst(str_replace('_', ' ', $name)); + } + ksort($list); + + return $list; + } + + /** + * @return array + */ + public function modularSelect() + { + $list = []; + foreach ($this->items as $name => $file) { + if (strpos($name, 'modular/') !== 0) { + continue; + } + $list[$name] = ucfirst(trim(str_replace('_', ' ', Utils::basename($name)))); + } + ksort($list); + + return $list; + } + + /** + * @param string $uri + * @return array + */ + private function findBlueprints($uri) + { + $options = [ + 'compare' => 'Filename', + 'pattern' => '|\.yaml$|', + 'filters' => [ + 'key' => '|\.yaml$|' + ], + 'key' => 'SubPathName', + 'value' => 'PathName', + ]; + + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + if ($locator->isStream($uri)) { + $options['value'] = 'Url'; + } + + return Folder::all($uri, $options); + } +} diff --git a/system/src/Grav/Common/Plugin.php b/system/src/Grav/Common/Plugin.php new file mode 100644 index 0000000..c832be7 --- /dev/null +++ b/system/src/Grav/Common/Plugin.php @@ -0,0 +1,472 @@ +name = $name; + $this->grav = $grav; + + if ($config) { + $this->setConfig($config); + } + } + + /** + * @return ClassLoader|null + * @internal + */ + final public function getAutoloader(): ?ClassLoader + { + return $this->loader; + } + + /** + * @param ClassLoader|null $loader + * @internal + */ + final public function setAutoloader(?ClassLoader $loader): void + { + $this->loader = $loader; + } + + /** + * @param Config $config + * @return $this + */ + public function setConfig(Config $config) + { + $this->config = $config; + + return $this; + } + + /** + * Get configuration of the plugin. + * + * @return array + */ + public function config() + { + return $this->config["plugins.{$this->name}"] ?? []; + } + + /** + * Determine if plugin is running under the admin + * + * @return bool + */ + public function isAdmin() + { + return Utils::isAdminPlugin(); + } + + /** + * Determine if plugin is running under the CLI + * + * @return bool + */ + public function isCli() + { + return defined('GRAV_CLI'); + } + + /** + * Determine if this route is in Admin and active for the plugin + * + * @param string $plugin_route + * @return bool + */ + protected function isPluginActiveAdmin($plugin_route) + { + $active = false; + + /** @var Uri $uri */ + $uri = $this->grav['uri']; + /** @var Config $config */ + $config = $this->config ?? $this->grav['config']; + + if (strpos($uri->path(), $config->get('plugins.admin.route') . '/' . $plugin_route) === false) { + $active = false; + } elseif (isset($uri->paths()[1]) && $uri->paths()[1] === $plugin_route) { + $active = true; + } + + return $active; + } + + /** + * @param array $events + * @return void + */ + protected function enable(array $events) + { + /** @var EventDispatcher $dispatcher */ + $dispatcher = $this->grav['events']; + + foreach ($events as $eventName => $params) { + if (is_string($params)) { + $dispatcher->addListener($eventName, [$this, $params]); + } elseif (is_string($params[0])) { + $dispatcher->addListener($eventName, [$this, $params[0]], $this->getPriority($params, $eventName)); + } else { + foreach ($params as $listener) { + $dispatcher->addListener($eventName, [$this, $listener[0]], $this->getPriority($listener, $eventName)); + } + } + } + } + + /** + * @param array $params + * @param string $eventName + * @return int + */ + private function getPriority($params, $eventName) + { + $override = implode('.', ['priorities', $this->name, $eventName, $params[0]]); + + return $this->grav['config']->get($override) ?? $params[1] ?? 0; + } + + /** + * @param array $events + * @return void + */ + protected function disable(array $events) + { + /** @var EventDispatcher $dispatcher */ + $dispatcher = $this->grav['events']; + + foreach ($events as $eventName => $params) { + if (is_string($params)) { + $dispatcher->removeListener($eventName, [$this, $params]); + } elseif (is_string($params[0])) { + $dispatcher->removeListener($eventName, [$this, $params[0]]); + } else { + foreach ($params as $listener) { + $dispatcher->removeListener($eventName, [$this, $listener[0]]); + } + } + } + } + + /** + * Whether or not an offset exists. + * + * @param string $offset An offset to check for. + * @return bool Returns TRUE on success or FALSE on failure. + */ + #[\ReturnTypeWillChange] + public function offsetExists($offset) + { + if ($offset === 'title') { + $offset = 'name'; + } + + $blueprint = $this->getBlueprint(); + + return isset($blueprint[$offset]); + } + + /** + * Returns the value at specified offset. + * + * @param string $offset The offset to retrieve. + * @return mixed Can return all value types. + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + if ($offset === 'title') { + $offset = 'name'; + } + + $blueprint = $this->getBlueprint(); + + return $blueprint[$offset] ?? null; + } + + /** + * Assigns a value to the specified offset. + * + * @param string $offset The offset to assign the value to. + * @param mixed $value The value to set. + * @throws LogicException + */ + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value) + { + throw new LogicException(__CLASS__ . ' blueprints cannot be modified.'); + } + + /** + * Unsets an offset. + * + * @param string $offset The offset to unset. + * @throws LogicException + */ + #[\ReturnTypeWillChange] + public function offsetUnset($offset) + { + throw new LogicException(__CLASS__ . ' blueprints cannot be modified.'); + } + + /** + * @return array + */ + public function __debugInfo(): array + { + $array = (array)$this; + + unset($array["\0*\0grav"]); + $array["\0*\0config"] = $this->config(); + + return $array; + } + + /** + * This function will search a string for markdown links in a specific format. The link value can be + * optionally compared against via the $internal_regex and operated on by the callback $function + * provided. + * + * format: [plugin:myplugin_name](function_data) + * + * @param string $content The string to perform operations upon + * @param callable $function The anonymous callback function + * @param string $internal_regex Optional internal regex to extra data from + * @return string + */ + protected function parseLinks($content, $function, $internal_regex = '(.*)') + { + $regex = '/\[plugin:(?:' . preg_quote($this->name, '/') . ')\]\(' . $internal_regex . '\)/i'; + + $result = preg_replace_callback($regex, $function, $content); + \assert($result !== null); + + return $result; + } + + /** + * Merge global and page configurations. + * + * WARNING: This method modifies page header! + * + * @param PageInterface $page The page to merge the configurations with the + * plugin settings. + * @param mixed $deep false = shallow|true = recursive|merge = recursive+unique + * @param array $params Array of additional configuration options to + * merge with the plugin settings. + * @param string $type Is this 'plugins' or 'themes' + * @return Data + */ + protected function mergeConfig(PageInterface $page, $deep = false, $params = [], $type = 'plugins') + { + /** @var Config $config */ + $config = $this->config ?? $this->grav['config']; + + $class_name = $this->name; + $class_name_merged = $class_name . '.merged'; + $defaults = $config->get($type . '.' . $class_name, []); + $page_header = $page->header(); + $header = []; + + if (!isset($page_header->{$class_name_merged}) && isset($page_header->{$class_name})) { + // Get default plugin configurations and retrieve page header configuration + $config = $page_header->{$class_name}; + if (is_bool($config)) { + // Overwrite enabled option with boolean value in page header + $config = ['enabled' => $config]; + } + // Merge page header settings using deep or shallow merging technique + $header = $this->mergeArrays($deep, $defaults, $config); + + // Create new config object and set it on the page object so it's cached for next time + $page->modifyHeader($class_name_merged, new Data($header)); + } elseif (isset($page_header->{$class_name_merged})) { + $merged = $page_header->{$class_name_merged}; + $header = $merged->toArray(); + } + if (empty($header)) { + $header = $defaults; + } + // Merge additional parameter with configuration options + $header = $this->mergeArrays($deep, $header, $params); + + // Return configurations as a new data config class + return new Data($header); + } + + /** + * Merge arrays based on deepness + * + * @param string|bool $deep + * @param array $array1 + * @param array $array2 + * @return array + */ + private function mergeArrays($deep, $array1, $array2) + { + if ($deep === 'merge') { + return Utils::arrayMergeRecursiveUnique($array1, $array2); + } + if ($deep === true) { + return array_replace_recursive($array1, $array2); + } + + return array_merge($array1, $array2); + } + + /** + * Persists to disk the plugin parameters currently stored in the Grav Config object + * + * @param string $name The name of the plugin whose config it should store. + * @return bool + */ + public static function saveConfig($name) + { + if (!$name) { + return false; + } + + $grav = Grav::instance(); + + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + + $filename = 'config://plugins/' . $name . '.yaml'; + $file = YamlFile::instance((string)$locator->findResource($filename, true, true)); + $content = $grav['config']->get('plugins.' . $name); + $file->save($content); + $file->free(); + unset($file); + + return true; + } + + public static function inheritedConfigOption(string $plugin, string $var, PageInterface $page = null, $default = null) + { + if (Utils::isAdminPlugin()) { + $page = Grav::instance()['admin']->page() ?? null; + } else { + $page = $page ?? Grav::instance()['page'] ?? null; + } + + // Try to find var in the page headers + if ($page instanceof PageInterface && $page->exists()) { + // Loop over pages and look for header vars + while ($page && !$page->root()) { + $header = new Data((array)$page->header()); + $value = $header->get("$plugin.$var"); + if (isset($value)) { + return $value; + } + $page = $page->parent(); + } + } + + return Grav::instance()['config']->get("plugins.$plugin.$var", $default); + } + + /** + * Simpler getter for the plugin blueprint + * + * @return Blueprint + */ + public function getBlueprint() + { + if (null === $this->blueprint) { + $this->loadBlueprint(); + \assert($this->blueprint instanceof Blueprint); + } + + return $this->blueprint; + } + + /** + * Load blueprints. + * + * @return void + */ + protected function loadBlueprint() + { + if (null === $this->blueprint) { + $grav = Grav::instance(); + /** @var Plugins $plugins */ + $plugins = $grav['plugins']; + $data = $plugins->get($this->name); + \assert($data !== null); + $this->blueprint = $data->blueprints(); + } + } +} diff --git a/system/src/Grav/Common/Plugins.php b/system/src/Grav/Common/Plugins.php new file mode 100644 index 0000000..3826bda --- /dev/null +++ b/system/src/Grav/Common/Plugins.php @@ -0,0 +1,330 @@ +getIterator('plugins://'); + + $plugins = []; + /** @var SplFileInfo $directory */ + foreach ($iterator as $directory) { + if (!$directory->isDir()) { + continue; + } + $plugins[] = $directory->getFilename(); + } + + sort($plugins, SORT_NATURAL | SORT_FLAG_CASE); + + foreach ($plugins as $plugin) { + $object = $this->loadPlugin($plugin); + if ($object) { + $this->add($object); + } + } + } + + /** + * @return $this + */ + public function setup() + { + $blueprints = []; + $formFields = []; + + $grav = Grav::instance(); + + /** @var Config $config */ + $config = $grav['config']; + + /** @var Plugin $plugin */ + foreach ($this->items as $plugin) { + // Setup only enabled plugins. + if ($config["plugins.{$plugin->name}.enabled"] && $plugin instanceof Plugin) { + if (isset($plugin->features['blueprints'])) { + $blueprints["plugin://{$plugin->name}/blueprints"] = $plugin->features['blueprints']; + } + if (method_exists($plugin, 'getFormFieldTypes')) { + $formFields[get_class($plugin)] = $plugin->features['formfields'] ?? 0; + } + } + } + + if ($blueprints) { + // Order by priority. + arsort($blueprints, SORT_NUMERIC); + + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + $locator->addPath('blueprints', '', array_keys($blueprints), ['system', 'blueprints']); + } + + if ($formFields) { + // Order by priority. + arsort($formFields, SORT_NUMERIC); + + $list = []; + foreach ($formFields as $className => $priority) { + $plugin = $this->items[$className]; + $list += $plugin->getFormFieldTypes(); + } + + $this->formFieldTypes = $list; + } + + return $this; + } + + /** + * Registers all plugins. + * + * @return Plugin[] array of Plugin objects + * @throws RuntimeException + */ + public function init() + { + if ($this->plugins_initialized) { + return $this->items; + } + + $grav = Grav::instance(); + + /** @var Config $config */ + $config = $grav['config']; + + /** @var EventDispatcher $events */ + $events = $grav['events']; + + foreach ($this->items as $instance) { + // Register only enabled plugins. + if ($config["plugins.{$instance->name}.enabled"] && $instance instanceof Plugin) { + // Set plugin configuration. + $instance->setConfig($config); + // Register autoloader. + if (method_exists($instance, 'autoload')) { + $instance->setAutoloader($instance->autoload()); + } + // Register event listeners. + $events->addSubscriber($instance); + } + } + + // Plugins Loaded Event + $event = new PluginsLoadedEvent($grav, $this); + $grav->dispatchEvent($event); + + $this->plugins_initialized = true; + + return $this->items; + } + + /** + * Add a plugin + * + * @param Plugin $plugin + * @return void + */ + public function add($plugin) + { + if (is_object($plugin)) { + $this->items[get_class($plugin)] = $plugin; + } + } + + /** + * @return array + */ + public function __debugInfo(): array + { + $array = (array)$this; + + unset($array["\0Grav\Common\Iterator\0iteratorUnset"]); + + return $array; + } + + /** + * @return Plugin[] Index of all plugins by plugin name. + */ + public static function getPlugins(): array + { + /** @var Plugins $plugins */ + $plugins = Grav::instance()['plugins']; + + $list = []; + foreach ($plugins as $instance) { + $list[$instance->name] = $instance; + } + + return $list; + } + + /** + * @param string $name Plugin name + * @return Plugin|null Plugin object or null if plugin cannot be found. + */ + public static function getPlugin(string $name) + { + $list = static::getPlugins(); + + return $list[$name] ?? null; + } + + /** + * Return list of all plugin data with their blueprints. + * + * @return Data[] + */ + public static function all() + { + $grav = Grav::instance(); + + /** @var Plugins $plugins */ + $plugins = $grav['plugins']; + $list = []; + + foreach ($plugins as $instance) { + $name = $instance->name; + + try { + $result = self::get($name); + } catch (Exception $e) { + $exception = new RuntimeException(sprintf('Plugin %s: %s', $name, $e->getMessage()), $e->getCode(), $e); + + /** @var Debugger $debugger */ + $debugger = $grav['debugger']; + $debugger->addMessage("Plugin {$name} cannot be loaded, please check Exceptions tab", 'error'); + $debugger->addException($exception); + + continue; + } + + if ($result) { + $list[$name] = $result; + } + } + + return $list; + } + + /** + * Get a plugin by name + * + * @param string $name + * @return Data|null + */ + public static function get($name) + { + $blueprints = new Blueprints('plugins://'); + $blueprint = $blueprints->get("{$name}/blueprints"); + + // Load default configuration. + $file = CompiledYamlFile::instance("plugins://{$name}/{$name}" . YAML_EXT); + + // ensure this is a valid plugin + if (!$file->exists()) { + return null; + } + + $obj = new Data((array)$file->content(), $blueprint); + + // Override with user configuration. + $obj->merge(Grav::instance()['config']->get('plugins.' . $name) ?: []); + + // Save configuration always to user/config. + $file = CompiledYamlFile::instance("config://plugins/{$name}.yaml"); + $obj->file($file); + + return $obj; + } + + /** + * @param string $name + * @return Plugin|null + */ + protected function loadPlugin($name) + { + // NOTE: ALL THE LOCAL VARIABLES ARE USED INSIDE INCLUDED FILE, DO NOT REMOVE THEM! + $grav = Grav::instance(); + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + $class = null; + + // Start by attempting to load the plugin_name.php file. + $file = $locator->findResource('plugins://' . $name . DS . $name . PLUGIN_EXT); + if (is_file($file)) { + // Local variables available in the file: $grav, $name, $file + $class = include_once $file; + if (!is_object($class) || !is_subclass_of($class, Plugin::class, true)) { + $class = null; + } + } + + // If the class hasn't been initialized yet, guess the class name and create a new instance. + if (null === $class) { + $className = Inflector::camelize($name); + $pluginClassFormat = [ + 'Grav\\Plugin\\' . ucfirst($name). 'Plugin', + 'Grav\\Plugin\\' . $className . 'Plugin', + 'Grav\\Plugin\\' . $className + ]; + + foreach ($pluginClassFormat as $pluginClass) { + if (is_subclass_of($pluginClass, Plugin::class, true)) { + $class = new $pluginClass($name, $grav); + break; + } + } + } + + // Log a warning if plugin cannot be found. + if (null === $class) { + $grav['log']->addWarning( + sprintf("Plugin '%s' enabled but not found! Try clearing cache with `bin/grav clearcache`", $name) + ); + } + + return $class; + } +} diff --git a/system/src/Grav/Common/Processors/AssetsProcessor.php b/system/src/Grav/Common/Processors/AssetsProcessor.php new file mode 100644 index 0000000..6ed8283 --- /dev/null +++ b/system/src/Grav/Common/Processors/AssetsProcessor.php @@ -0,0 +1,41 @@ +startTimer(); + $this->container['assets']->init(); + $this->container->fireEvent('onAssetsInitialized'); + $this->stopTimer(); + + return $handler->handle($request); + } +} diff --git a/system/src/Grav/Common/Processors/BackupsProcessor.php b/system/src/Grav/Common/Processors/BackupsProcessor.php new file mode 100644 index 0000000..deb4da7 --- /dev/null +++ b/system/src/Grav/Common/Processors/BackupsProcessor.php @@ -0,0 +1,41 @@ +startTimer(); + $backups = $this->container['backups']; + $backups->init(); + $this->stopTimer(); + + return $handler->handle($request); + } +} diff --git a/system/src/Grav/Common/Processors/DebuggerAssetsProcessor.php b/system/src/Grav/Common/Processors/DebuggerAssetsProcessor.php new file mode 100644 index 0000000..530c7f2 --- /dev/null +++ b/system/src/Grav/Common/Processors/DebuggerAssetsProcessor.php @@ -0,0 +1,40 @@ +startTimer(); + $this->container['debugger']->addAssets(); + $this->stopTimer(); + + return $handler->handle($request); + } +} diff --git a/system/src/Grav/Common/Processors/Events/RequestHandlerEvent.php b/system/src/Grav/Common/Processors/Events/RequestHandlerEvent.php new file mode 100644 index 0000000..e84db7e --- /dev/null +++ b/system/src/Grav/Common/Processors/Events/RequestHandlerEvent.php @@ -0,0 +1,82 @@ +offsetGet('request'); + } + + /** + * @return Route + */ + public function getRoute(): Route + { + return $this->getRequest()->getAttribute('route'); + } + + /** + * @return RequestHandler + */ + public function getHandler(): RequestHandler + { + return $this->offsetGet('handler'); + } + + /** + * @return ResponseInterface|null + */ + public function getResponse(): ?ResponseInterface + { + return $this->offsetGet('response'); + } + + /** + * @param ResponseInterface $response + * @return $this + */ + public function setResponse(ResponseInterface $response): self + { + $this->offsetSet('response', $response); + $this->stopPropagation(); + + return $this; + } + + /** + * @param string $name + * @param MiddlewareInterface $middleware + * @return RequestHandlerEvent + */ + public function addMiddleware(string $name, MiddlewareInterface $middleware): self + { + /** @var RequestHandler $handler */ + $handler = $this['handler']; + $handler->addMiddleware($name, $middleware); + + return $this; + } +} diff --git a/system/src/Grav/Common/Processors/InitializeProcessor.php b/system/src/Grav/Common/Processors/InitializeProcessor.php new file mode 100644 index 0000000..43a88d0 --- /dev/null +++ b/system/src/Grav/Common/Processors/InitializeProcessor.php @@ -0,0 +1,461 @@ +processCli(); + } + } + + /** + * @param ServerRequestInterface $request + * @param RequestHandlerInterface $handler + * @return ResponseInterface + */ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $this->startTimer('_init', 'Initialize'); + + // Load configuration. + $config = $this->initializeConfig(); + + // Initialize logger. + $this->initializeLogger($config); + + // Initialize error handlers. + $this->initializeErrors(); + + // Initialize debugger. + $debugger = $this->initializeDebugger(); + + // Debugger can return response right away. + $response = $this->handleDebuggerRequest($debugger, $request); + if ($response) { + $this->stopTimer('_init'); + + return $response; + } + + // Initialize output buffering. + $this->initializeOutputBuffering($config); + + // Set timezone, locale. + $this->initializeLocale($config); + + // Load plugins. + $this->initializePlugins(); + + // Load pages. + $this->initializePages($config); + + // Load accounts (decides class to be used). + // TODO: remove in 2.0. + $this->container['accounts']; + + // Initialize session (used by URI, see issue #3269). + $this->initializeSession($config); + + // Initialize URI (uses session, see issue #3269). + $this->initializeUri($config); + + // Grav may return redirect response right away. + $redirectCode = (int)$config->get('system.pages.redirect_trailing_slash', 1); + if ($redirectCode) { + $response = $this->handleRedirectRequest($request, $redirectCode > 300 ? $redirectCode : null); + if ($response) { + $this->stopTimer('_init'); + + return $response; + } + } + + $this->stopTimer('_init'); + + // Wrap call to next handler so that debugger can profile it. + /** @var Response $response */ + $response = $debugger->profile(static function () use ($handler, $request) { + return $handler->handle($request); + }); + + // Log both request and response and return the response. + return $debugger->logRequest($request, $response); + } + + public function processCli(): void + { + // Load configuration. + $config = $this->initializeConfig(); + + // Initialize logger. + $this->initializeLogger($config); + + // Disable debugger. + $this->container['debugger']->enabled(false); + + // Set timezone, locale. + $this->initializeLocale($config); + + // Load plugins. + $this->initializePlugins(); + + // Load pages. + $this->initializePages($config); + + // Initialize URI. + $this->initializeUri($config); + + // Load accounts (decides class to be used). + // TODO: remove in 2.0. + $this->container['accounts']; + } + + /** + * @return Config + */ + protected function initializeConfig(): Config + { + $this->startTimer('_init_config', 'Configuration'); + + // Initialize Configuration + $grav = $this->container; + + /** @var Config $config */ + $config = $grav['config']; + $config->init(); + $grav['plugins']->setup(); + + if (defined('GRAV_SCHEMA') && $config->get('versions') === null) { + $filename = USER_DIR . 'config/versions.yaml'; + if (!is_file($filename)) { + $versions = [ + 'core' => [ + 'grav' => [ + 'version' => GRAV_VERSION, + 'schema' => GRAV_SCHEMA + ] + ] + ]; + $config->set('versions', $versions); + + $file = new YamlFile($filename, new YamlFormatter(['inline' => 4])); + $file->save($versions); + } + } + + // Override configuration using the environment. + $prefix = 'GRAV_CONFIG'; + $env = getenv($prefix); + if ($env) { + $cPrefix = $prefix . '__'; + $aPrefix = $prefix . '_ALIAS__'; + $cLen = strlen($cPrefix); + $aLen = strlen($aPrefix); + + $keys = $aliases = []; + $env = $_ENV + $_SERVER; + foreach ($env as $key => $value) { + if (!str_starts_with($key, $prefix)) { + continue; + } + if (str_starts_with($key, $cPrefix)) { + $key = str_replace('__', '.', substr($key, $cLen)); + $keys[$key] = $value; + } elseif (str_starts_with($key, $aPrefix)) { + $key = substr($key, $aLen); + $aliases[$key] = $value; + } + } + $list = []; + foreach ($keys as $key => $value) { + foreach ($aliases as $alias => $real) { + $key = str_replace($alias, $real, $key); + } + $list[$key] = $value; + $config->set($key, $value); + } + } + + $this->stopTimer('_init_config'); + + return $config; + } + + /** + * @param Config $config + * @return Logger + */ + protected function initializeLogger(Config $config): Logger + { + $this->startTimer('_init_logger', 'Logger'); + + $grav = $this->container; + + // Initialize Logging + /** @var Logger $log */ + $log = $grav['log']; + + if ($config->get('system.log.handler', 'file') === 'syslog') { + $log->popHandler(); + + $facility = $config->get('system.log.syslog.facility', 'local6'); + $tag = $config->get('system.log.syslog.tag', 'grav'); + $logHandler = new SyslogHandler($tag, $facility); + $formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%"); + $logHandler->setFormatter($formatter); + + $log->pushHandler($logHandler); + } + + $this->stopTimer('_init_logger'); + + return $log; + } + + /** + * @return Errors + */ + protected function initializeErrors(): Errors + { + $this->startTimer('_init_errors', 'Error Handlers Reset'); + + $grav = $this->container; + + // Initialize Error Handlers + /** @var Errors $errors */ + $errors = $grav['errors']; + $errors->resetHandlers(); + + $this->stopTimer('_init_errors'); + + return $errors; + } + + /** + * @return Debugger + */ + protected function initializeDebugger(): Debugger + { + $this->startTimer('_init_debugger', 'Init Debugger'); + + $grav = $this->container; + + /** @var Debugger $debugger */ + $debugger = $grav['debugger']; + $debugger->init(); + + $this->stopTimer('_init_debugger'); + + return $debugger; + } + + /** + * @param Debugger $debugger + * @param ServerRequestInterface $request + * @return ResponseInterface|null + */ + protected function handleDebuggerRequest(Debugger $debugger, ServerRequestInterface $request): ?ResponseInterface + { + // Clockwork integration. + $clockwork = $debugger->getClockwork(); + if ($clockwork) { + $server = $request->getServerParams(); +// $baseUri = str_replace('\\', '/', dirname(parse_url($server['SCRIPT_NAME'], PHP_URL_PATH))); +// if ($baseUri === '/') { +// $baseUri = ''; +// } + $requestTime = $server['REQUEST_TIME_FLOAT'] ?? GRAV_REQUEST_TIME; + + $request = $request->withAttribute('request_time', $requestTime); + + // Handle clockwork API calls. + $uri = $request->getUri(); + if (Utils::contains($uri->getPath(), '/__clockwork/')) { + return $debugger->debuggerRequest($request); + } + + $this->container['clockwork'] = $clockwork; + } + + return null; + } + + /** + * @param Config $config + */ + protected function initializeOutputBuffering(Config $config): void + { + $this->startTimer('_init_ob', 'Initialize Output Buffering'); + + // Use output buffering to prevent headers from being sent too early. + ob_start(); + if ($config->get('system.cache.gzip') && !@ob_start('ob_gzhandler')) { + // Enable zip/deflate with a fallback in case of if browser does not support compressing. + ob_start(); + } + + $this->stopTimer('_init_ob'); + } + + /** + * @param Config $config + */ + protected function initializeLocale(Config $config): void + { + $this->startTimer('_init_locale', 'Initialize Locale'); + + // Initialize the timezone. + $timezone = $config->get('system.timezone'); + if ($timezone) { + date_default_timezone_set($timezone); + } + + $grav = $this->container; + $grav->setLocale(); + + $this->stopTimer('_init_locale'); + } + + protected function initializePlugins(): Plugins + { + $this->startTimer('_init_plugins_load', 'Load Plugins'); + + $grav = $this->container; + + /** @var Plugins $plugins */ + $plugins = $grav['plugins']; + $plugins->init(); + + $this->stopTimer('_init_plugins_load'); + + return $plugins; + } + + protected function initializePages(Config $config): Pages + { + $this->startTimer('_init_pages_register', 'Load Pages'); + + $grav = $this->container; + + /** @var Pages $pages */ + $pages = $grav['pages']; + // Upgrading from older Grav versions won't work without checking if the method exists. + if (method_exists($pages, 'register')) { + $pages->register(); + } + + $this->stopTimer('_init_pages_register'); + + return $pages; + } + + + protected function initializeUri(Config $config): void + { + $this->startTimer('_init_uri', 'Initialize URI'); + + $grav = $this->container; + + /** @var Uri $uri */ + $uri = $grav['uri']; + $uri->init(); + + $this->stopTimer('_init_uri'); + } + + protected function handleRedirectRequest(RequestInterface $request, int $code = null): ?ResponseInterface + { + if (!in_array($request->getMethod(), ['GET', 'HEAD'])) { + return null; + } + + // Redirect pages with trailing slash if configured to do so. + $uri = $request->getUri(); + $path = $uri->getPath() ?: '/'; + $root = $this->container['uri']->rootUrl(); + + if ($path !== $root && $path !== $root . '/' && Utils::endsWith($path, '/')) { + // Use permanent redirect for SEO reasons. + return $this->container->getRedirectResponse((string)$uri->withPath(rtrim($path, '/')), $code); + } + + return null; + } + + /** + * @param Config $config + */ + protected function initializeSession(Config $config): void + { + // FIXME: Initialize session should happen later after plugins have been loaded. This is a workaround to fix session issues in AWS. + if (isset($this->container['session']) && $config->get('system.session.initialize', true)) { + $this->startTimer('_init_session', 'Start Session'); + + /** @var Session $session */ + $session = $this->container['session']; + + try { + $session->init(); + } catch (SessionException $e) { + $session->init(); + $message = 'Session corruption detected, restarting session...'; + $this->addMessage($message); + $this->container['messages']->add($message, 'error'); + } + + $this->stopTimer('_init_session'); + } + } +} diff --git a/system/src/Grav/Common/Processors/PagesProcessor.php b/system/src/Grav/Common/Processors/PagesProcessor.php new file mode 100644 index 0000000..fb8e896 --- /dev/null +++ b/system/src/Grav/Common/Processors/PagesProcessor.php @@ -0,0 +1,115 @@ +startTimer(); + + // Dump Cache state + $this->container['debugger']->addMessage($this->container['cache']->getCacheStatus()); + + $this->container['pages']->init(); + + $route = $this->container['route']; + + $this->container->fireEvent('onPagesInitialized', new Event( + [ + 'pages' => $this->container['pages'], + 'route' => $route, + 'request' => $request + ] + )); + $this->container->fireEvent('onPageInitialized', new Event( + [ + 'page' => $this->container['page'], + 'route' => $route, + 'request' => $request + ] + )); + + /** @var PageInterface $page */ + $page = $this->container['page']; + + if (!$page->routable()) { + $exception = new RequestException($request, 'Page Not Found', 404); + // If no page found, fire event + $event = new PageEvent([ + 'page' => $page, + 'code' => $exception->getCode(), + 'message' => $exception->getMessage(), + 'exception' => $exception, + 'route' => $route, + 'request' => $request + ]); + $event->page = null; + $event = $this->container->fireEvent('onPageNotFound', $event); + + if (isset($event->page)) { + unset($this->container['page']); + $this->container['page'] = $page = $event->page; + } else { + throw new RuntimeException('Page Not Found', 404); + } + + $this->addMessage("Routed to page {$page->rawRoute()} (type: {$page->template()}) [Not Found fallback]"); + } else { + $this->addMessage("Routed to page {$page->rawRoute()} (type: {$page->template()})"); + + $task = $this->container['task']; + $action = $this->container['action']; + + /** @var Forms $forms */ + $forms = $this->container['forms'] ?? null; + $form = $forms ? $forms->getActiveForm() : null; + + $options = ['page' => $page, 'form' => $form, 'request' => $request]; + if ($task) { + $event = new Event(['task' => $task] + $options); + $this->container->fireEvent('onPageTask', $event); + $this->container->fireEvent('onPageTask.' . $task, $event); + } elseif ($action) { + $event = new Event(['action' => $action] + $options); + $this->container->fireEvent('onPageAction', $event); + $this->container->fireEvent('onPageAction.' . $action, $event); + } + } + + $this->stopTimer(); + + return $handler->handle($request); + } +} diff --git a/system/src/Grav/Common/Processors/PluginsProcessor.php b/system/src/Grav/Common/Processors/PluginsProcessor.php new file mode 100644 index 0000000..a6c2865 --- /dev/null +++ b/system/src/Grav/Common/Processors/PluginsProcessor.php @@ -0,0 +1,41 @@ +startTimer(); + $grav = $this->container; + $grav->fireEvent('onPluginsInitialized'); + $this->stopTimer(); + + return $handler->handle($request); + } +} diff --git a/system/src/Grav/Common/Processors/ProcessorBase.php b/system/src/Grav/Common/Processors/ProcessorBase.php new file mode 100644 index 0000000..d4a0620 --- /dev/null +++ b/system/src/Grav/Common/Processors/ProcessorBase.php @@ -0,0 +1,70 @@ +container = $container; + } + + /** + * @param string|null $id + * @param string|null $title + */ + protected function startTimer($id = null, $title = null): void + { + /** @var Debugger $debugger */ + $debugger = $this->container['debugger']; + $debugger->startTimer($id ?? $this->id, $title ?? $this->title); + } + + /** + * @param string|null $id + */ + protected function stopTimer($id = null): void + { + /** @var Debugger $debugger */ + $debugger = $this->container['debugger']; + $debugger->stopTimer($id ?? $this->id); + } + + /** + * @param string $message + * @param string $label + * @param bool $isString + */ + protected function addMessage($message, $label = 'info', $isString = true): void + { + /** @var Debugger $debugger */ + $debugger = $this->container['debugger']; + $debugger->addMessage($message, $label, $isString); + } +} diff --git a/system/src/Grav/Common/Processors/ProcessorInterface.php b/system/src/Grav/Common/Processors/ProcessorInterface.php new file mode 100644 index 0000000..2d99e3f --- /dev/null +++ b/system/src/Grav/Common/Processors/ProcessorInterface.php @@ -0,0 +1,20 @@ +startTimer(); + + $container = $this->container; + $output = $container['output']; + + if ($output instanceof ResponseInterface) { + return $output; + } + + /** @var PageInterface $page */ + $page = $this->container['page']; + + // Use internal Grav output. + $container->output = $output; + + ob_start(); + + $event = new Event(['page' => $page, 'output' => &$container->output]); + $container->fireEvent('onOutputGenerated', $event); + + echo $container->output; + + $html = ob_get_clean(); + + // remove any output + $container->output = ''; + + $event = new Event(['page' => $page, 'output' => $html]); + $this->container->fireEvent('onOutputRendered', $event); + + $this->stopTimer(); + + return new Response($page->httpResponseCode(), $page->httpHeaders(), $html); + } +} diff --git a/system/src/Grav/Common/Processors/RequestProcessor.php b/system/src/Grav/Common/Processors/RequestProcessor.php new file mode 100644 index 0000000..a69ce3d --- /dev/null +++ b/system/src/Grav/Common/Processors/RequestProcessor.php @@ -0,0 +1,66 @@ +startTimer(); + + $header = $request->getHeaderLine('Content-Type'); + $type = trim(strstr($header, ';', true) ?: $header); + if ($type === 'application/json') { + $request = $request->withParsedBody(json_decode($request->getBody()->getContents(), true)); + } + + $uri = $request->getUri(); + $ext = mb_strtolower(Utils::pathinfo($uri->getPath(), PATHINFO_EXTENSION)); + + $request = $request + ->withAttribute('grav', $this->container) + ->withAttribute('time', $_SERVER['REQUEST_TIME_FLOAT'] ?? GRAV_REQUEST_TIME) + ->withAttribute('route', Uri::getCurrentRoute()->withExtension($ext)) + ->withAttribute('referrer', $this->container['uri']->referrer()); + + $event = new RequestHandlerEvent(['request' => $request, 'handler' => $handler]); + /** @var RequestHandlerEvent $event */ + $event = $this->container->fireEvent('onRequestHandlerInit', $event); + $response = $event->getResponse(); + $this->stopTimer(); + + if ($response) { + return $response; + } + + return $handler->handle($request); + } +} diff --git a/system/src/Grav/Common/Processors/SchedulerProcessor.php b/system/src/Grav/Common/Processors/SchedulerProcessor.php new file mode 100644 index 0000000..264f0b2 --- /dev/null +++ b/system/src/Grav/Common/Processors/SchedulerProcessor.php @@ -0,0 +1,42 @@ +startTimer(); + $scheduler = $this->container['scheduler']; + $this->container->fireEvent('onSchedulerInitialized', new Event(['scheduler' => $scheduler])); + $this->stopTimer(); + + return $handler->handle($request); + } +} diff --git a/system/src/Grav/Common/Processors/TasksProcessor.php b/system/src/Grav/Common/Processors/TasksProcessor.php new file mode 100644 index 0000000..73540b8 --- /dev/null +++ b/system/src/Grav/Common/Processors/TasksProcessor.php @@ -0,0 +1,71 @@ +startTimer(); + + $task = $this->container['task']; + $action = $this->container['action']; + if ($task || $action) { + $attributes = $request->getAttribute('controller'); + + $controllerClass = $attributes['class'] ?? null; + if ($controllerClass) { + /** @var RequestHandlerInterface $controller */ + $controller = new $controllerClass($attributes['path'] ?? '', $attributes['params'] ?? []); + try { + $response = $controller->handle($request); + + if ($response->getStatusCode() === 418) { + $response = $handler->handle($request); + } + + $this->stopTimer(); + + return $response; + } catch (NotFoundException $e) { + // Task not found: Let it pass through. + } + } + + if ($task) { + $this->container->fireEvent('onTask.' . $task); + } elseif ($action) { + $this->container->fireEvent('onAction.' . $action); + } + } + $this->stopTimer(); + + return $handler->handle($request); + } +} diff --git a/system/src/Grav/Common/Processors/ThemesProcessor.php b/system/src/Grav/Common/Processors/ThemesProcessor.php new file mode 100644 index 0000000..e2a68a6 --- /dev/null +++ b/system/src/Grav/Common/Processors/ThemesProcessor.php @@ -0,0 +1,40 @@ +startTimer(); + $this->container['themes']->init(); + $this->stopTimer(); + + return $handler->handle($request); + } +} diff --git a/system/src/Grav/Common/Processors/TwigProcessor.php b/system/src/Grav/Common/Processors/TwigProcessor.php new file mode 100644 index 0000000..e4722b1 --- /dev/null +++ b/system/src/Grav/Common/Processors/TwigProcessor.php @@ -0,0 +1,40 @@ +startTimer(); + $this->container['twig']->init(); + $this->stopTimer(); + + return $handler->handle($request); + } +} diff --git a/system/src/Grav/Common/Scheduler/Cron.php b/system/src/Grav/Common/Scheduler/Cron.php new file mode 100644 index 0000000..1de43f0 --- /dev/null +++ b/system/src/Grav/Common/Scheduler/Cron.php @@ -0,0 +1,577 @@ + modified for Grav integration + * @copyright Copyright (c) 2015 - 2025 Trilby Media, LLC. All rights reserved. + * @license MIT License; see LICENSE file for details. + */ + +namespace Grav\Common\Scheduler; + +/* + * Usage examples : + * ---------------- + * + * $cron = new Cron('10-30/5 12 * * *'); + * + * var_dump($cron->getMinutes()); + * // array(5) { + * // [0]=> int(10) + * // [1]=> int(15) + * // [2]=> int(20) + * // [3]=> int(25) + * // [4]=> int(30) + * // } + * + * var_dump($cron->getText('fr')); + * // string(32) "Chaque jour à 12:10,15,20,25,30" + * + * var_dump($cron->getText('en')); + * // string(30) "Every day at 12:10,15,20,25,30" + * + * var_dump($cron->getType()); + * // string(3) "day" + * + * var_dump($cron->getCronHours()); + * // string(2) "12" + * + * var_dump($cron->matchExact(new \DateTime('2012-07-01 13:25:10'))); + * // bool(false) + * + * var_dump($cron->matchExact(new \DateTime('2012-07-01 12:15:20'))); + * // bool(true) + * + * var_dump($cron->matchWithMargin(new \DateTime('2012-07-01 12:32:50'), -3, 5)); + * // bool(true) + */ + +use DateInterval; +use DateTime; +use RuntimeException; +use function count; +use function in_array; +use function is_array; +use function is_string; + +class Cron +{ + public const TYPE_UNDEFINED = ''; + public const TYPE_MINUTE = 'minute'; + public const TYPE_HOUR = 'hour'; + public const TYPE_DAY = 'day'; + public const TYPE_WEEK = 'week'; + public const TYPE_MONTH = 'month'; + public const TYPE_YEAR = 'year'; + /** + * + * @var array + */ + protected $texts = [ + 'fr' => [ + 'empty' => '-tout-', + 'name_minute' => 'minute', + 'name_hour' => 'heure', + 'name_day' => 'jour', + 'name_week' => 'semaine', + 'name_month' => 'mois', + 'name_year' => 'année', + 'text_period' => 'Chaque %s', + 'text_mins' => 'à %s minutes', + 'text_time' => 'à %02s:%02s', + 'text_dow' => 'le %s', + 'text_month' => 'de %s', + 'text_dom' => 'le %s', + 'weekdays' => ['lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi', 'dimanche'], + 'months' => ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'], + ], + 'en' => [ + 'empty' => '-all-', + 'name_minute' => 'minute', + 'name_hour' => 'hour', + 'name_day' => 'day', + 'name_week' => 'week', + 'name_month' => 'month', + 'name_year' => 'year', + 'text_period' => 'Every %s', + 'text_mins' => 'at %s minutes past the hour', + 'text_time' => 'at %02s:%02s', + 'text_dow' => 'on %s', + 'text_month' => 'of %s', + 'text_dom' => 'on the %s', + 'weekdays' => ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'], + 'months' => ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'], + ], + ]; + + /** + * min hour dom month dow + * @var string + */ + protected $cron = ''; + /** + * + * @var array + */ + protected $minutes = []; + /** + * + * @var array + */ + protected $hours = []; + /** + * + * @var array + */ + protected $months = []; + /** + * 0-7 : sunday, monday, ... saturday, sunday + * @var array + */ + protected $dow = []; + /** + * + * @var array + */ + protected $dom = []; + + /** + * @param string|null $cron + */ + public function __construct($cron = null) + { + if (null !== $cron) { + $this->setCron($cron); + } + } + + /** + * @return string + */ + public function getCron() + { + return implode(' ', [ + $this->getCronMinutes(), + $this->getCronHours(), + $this->getCronDaysOfMonth(), + $this->getCronMonths(), + $this->getCronDaysOfWeek(), + ]); + } + + /** + * @param string $lang 'fr' or 'en' + * @return string + */ + public function getText($lang) + { + // check lang + if (!isset($this->texts[$lang])) { + return $this->getCron(); + } + + $texts = $this->texts[$lang]; + // check type + + $type = $this->getType(); + if ($type === self::TYPE_UNDEFINED) { + return $this->getCron(); + } + + // init + $elements = []; + $elements[] = sprintf($texts['text_period'], $texts['name_' . $type]); + + // hour + if ($type === self::TYPE_HOUR) { + $elements[] = sprintf($texts['text_mins'], $this->getCronMinutes()); + } + + // week + if ($type === self::TYPE_WEEK) { + $dow = $this->getCronDaysOfWeek(); + foreach ($texts['weekdays'] as $i => $wd) { + $dow = str_replace((string) ($i + 1), $wd, $dow); + } + $elements[] = sprintf($texts['text_dow'], $dow); + } + + // month + year + if (in_array($type, [self::TYPE_MONTH, self::TYPE_YEAR], true)) { + $elements[] = sprintf($texts['text_dom'], $this->getCronDaysOfMonth()); + } + + // year + if ($type === self::TYPE_YEAR) { + $months = $this->getCronMonths(); + for ($i = count($texts['months']) - 1; $i >= 0; $i--) { + $months = str_replace((string) ($i + 1), $texts['months'][$i], $months); + } + $elements[] = sprintf($texts['text_month'], $months); + } + + // day + week + month + year + if (in_array($type, [self::TYPE_DAY, self::TYPE_WEEK, self::TYPE_MONTH, self::TYPE_YEAR], true)) { + $elements[] = sprintf($texts['text_time'], $this->getCronHours(), $this->getCronMinutes()); + } + + return str_replace('*', $texts['empty'], implode(' ', $elements)); + } + + /** + * @return string + */ + public function getType() + { + $mask = preg_replace('/[^\* ]/', '-', $this->getCron()); + $mask = preg_replace('/-+/', '-', $mask); + $mask = preg_replace('/[^-\*]/', '', $mask); + + if ($mask === '*****') { + return self::TYPE_MINUTE; + } + + if ($mask === '-****') { + return self::TYPE_HOUR; + } + + if (substr($mask, -3) === '***') { + return self::TYPE_DAY; + } + + if (substr($mask, -3) === '-**') { + return self::TYPE_MONTH; + } + + if (substr($mask, -3) === '**-') { + return self::TYPE_WEEK; + } + + if (substr($mask, -2) === '-*') { + return self::TYPE_YEAR; + } + + return self::TYPE_UNDEFINED; + } + + /** + * @param string $cron + * @return $this + */ + public function setCron($cron) + { + // sanitize + $cron = trim($cron); + $cron = preg_replace('/\s+/', ' ', $cron); + // explode + $elements = explode(' ', $cron); + if (count($elements) !== 5) { + throw new RuntimeException('Bad number of elements'); + } + + $this->cron = $cron; + $this->setMinutes($elements[0]); + $this->setHours($elements[1]); + $this->setDaysOfMonth($elements[2]); + $this->setMonths($elements[3]); + $this->setDaysOfWeek($elements[4]); + + return $this; + } + + /** + * @return string + */ + public function getCronMinutes() + { + return $this->arrayToCron($this->minutes); + } + + /** + * @return string + */ + public function getCronHours() + { + return $this->arrayToCron($this->hours); + } + + /** + * @return string + */ + public function getCronDaysOfMonth() + { + return $this->arrayToCron($this->dom); + } + + /** + * @return string + */ + public function getCronMonths() + { + return $this->arrayToCron($this->months); + } + + /** + * @return string + */ + public function getCronDaysOfWeek() + { + return $this->arrayToCron($this->dow); + } + + /** + * @return array + */ + public function getMinutes() + { + return $this->minutes; + } + + /** + * @return array + */ + public function getHours() + { + return $this->hours; + } + + /** + * @return array + */ + public function getDaysOfMonth() + { + return $this->dom; + } + + /** + * @return array + */ + public function getMonths() + { + return $this->months; + } + + /** + * @return array + */ + public function getDaysOfWeek() + { + return $this->dow; + } + + /** + * @param string|string[] $minutes + * @return $this + */ + public function setMinutes($minutes) + { + $this->minutes = $this->cronToArray($minutes, 0, 59); + + return $this; + } + + /** + * @param string|string[] $hours + * @return $this + */ + public function setHours($hours) + { + $this->hours = $this->cronToArray($hours, 0, 23); + + return $this; + } + + /** + * @param string|string[] $months + * @return $this + */ + public function setMonths($months) + { + $this->months = $this->cronToArray($months, 1, 12); + + return $this; + } + + /** + * @param string|string[] $dow + * @return $this + */ + public function setDaysOfWeek($dow) + { + $this->dow = $this->cronToArray($dow, 0, 7); + + return $this; + } + + /** + * @param string|string[] $dom + * @return $this + */ + public function setDaysOfMonth($dom) + { + $this->dom = $this->cronToArray($dom, 1, 31); + + return $this; + } + + /** + * @param mixed $date + * @param int $min + * @param int $hour + * @param int $day + * @param int $month + * @param int $weekday + * @return DateTime + */ + protected function parseDate($date, &$min, &$hour, &$day, &$month, &$weekday) + { + if (is_numeric($date) && (int)$date == $date) { + $date = new DateTime('@' . $date); + } elseif (is_string($date)) { + $date = new DateTime('@' . strtotime($date)); + } + if ($date instanceof DateTime) { + $min = (int)$date->format('i'); + $hour = (int)$date->format('H'); + $day = (int)$date->format('d'); + $month = (int)$date->format('m'); + $weekday = (int)$date->format('w'); // 0-6 + } else { + throw new RuntimeException('Date format not supported'); + } + + return new DateTime($date->format('Y-m-d H:i:sP')); + } + + /** + * @param int|string|DateTime $date + */ + public function matchExact($date) + { + $date = $this->parseDate($date, $min, $hour, $day, $month, $weekday); + + return + (empty($this->minutes) || in_array($min, $this->minutes, true)) && + (empty($this->hours) || in_array($hour, $this->hours, true)) && + (empty($this->dom) || in_array($day, $this->dom, true)) && + (empty($this->months) || in_array($month, $this->months, true)) && + (empty($this->dow) || in_array($weekday, $this->dow, true) || ($weekday == 0 && in_array(7, $this->dow, true)) || ($weekday == 7 && in_array(0, $this->dow, true)) + ); + } + + /** + * @param int|string|DateTime $date + * @param int $minuteBefore + * @param int $minuteAfter + */ + public function matchWithMargin($date, $minuteBefore = 0, $minuteAfter = 0) + { + if ($minuteBefore > 0) { + throw new RuntimeException('MinuteBefore parameter cannot be positive !'); + } + if ($minuteAfter < 0) { + throw new RuntimeException('MinuteAfter parameter cannot be negative !'); + } + + $date = $this->parseDate($date, $min, $hour, $day, $month, $weekday); + $interval = new DateInterval('PT1M'); // 1 min + if ($minuteBefore !== 0) { + $date->sub(new DateInterval('PT' . abs($minuteBefore) . 'M')); + } + $n = $minuteAfter - $minuteBefore + 1; + for ($i = 0; $i < $n; $i++) { + if ($this->matchExact($date)) { + return true; + } + $date->add($interval); + } + + return false; + } + + /** + * @param array $array + * @return string + */ + protected function arrayToCron($array) + { + $n = count($array); + if (!is_array($array) || $n === 0) { + return '*'; + } + + $cron = [$array[0]]; + $s = $c = $array[0]; + for ($i = 1; $i < $n; $i++) { + if ($array[$i] == $c + 1) { + $c = $array[$i]; + $cron[count($cron) - 1] = $s . '-' . $c; + } else { + $s = $c = $array[$i]; + $cron[] = $c; + } + } + + return implode(',', $cron); + } + + /** + * + * @param array|string $string + * @param int $min + * @param int $max + * @return array + */ + protected function cronToArray($string, $min, $max) + { + $array = []; + if (is_array($string)) { + foreach ($string as $val) { + if (is_numeric($val) && (int)$val == $val && $val >= $min && $val <= $max) { + $array[] = (int)$val; + } + } + } elseif ($string !== '*') { + while ($string !== '') { + // test "*/n" expression + if (preg_match('/^\*\/([0-9]+),?/', $string, $m)) { + for ($i = max(0, $min); $i <= min(59, $max); $i += $m[1]) { + $array[] = (int)$i; + } + $string = substr($string, strlen($m[0])); + continue; + } + // test "a-b/n" expression + if (preg_match('/^([0-9]+)-([0-9]+)\/([0-9]+),?/', $string, $m)) { + for ($i = max($m[1], $min); $i <= min($m[2], $max); $i += $m[3]) { + $array[] = (int)$i; + } + $string = substr($string, strlen($m[0])); + continue; + } + // test "a-b" expression + if (preg_match('/^([0-9]+)-([0-9]+),?/', $string, $m)) { + for ($i = max($m[1], $min); $i <= min($m[2], $max); $i++) { + $array[] = (int)$i; + } + $string = substr($string, strlen($m[0])); + continue; + } + // test "c" expression + if (preg_match('/^([0-9]+),?/', $string, $m)) { + if ($m[1] >= $min && $m[1] <= $max) { + $array[] = (int)$m[1]; + } + $string = substr($string, strlen($m[0])); + continue; + } + + // something goes wrong in the expression + return []; + } + } + sort($array, SORT_NUMERIC); + + return $array; + } +} diff --git a/system/src/Grav/Common/Scheduler/IntervalTrait.php b/system/src/Grav/Common/Scheduler/IntervalTrait.php new file mode 100644 index 0000000..55b1d89 --- /dev/null +++ b/system/src/Grav/Common/Scheduler/IntervalTrait.php @@ -0,0 +1,404 @@ +at = $expression; + $this->executionTime = CronExpression::factory($expression); + + return $this; + } + + /** + * Set the execution time to every minute. + * + * @return self + */ + public function everyMinute() + { + return $this->at('* * * * *'); + } + + /** + * Set the execution time to every hour. + * + * @param int|string $minute + * @return self + */ + public function hourly($minute = 0) + { + $c = $this->validateCronSequence($minute); + + return $this->at("{$c['minute']} * * * *"); + } + + /** + * Set the execution time to once a day. + * + * @param int|string $hour + * @param int|string $minute + * @return self + */ + public function daily($hour = 0, $minute = 0) + { + if (is_string($hour)) { + $parts = explode(':', $hour); + $hour = $parts[0]; + $minute = $parts[1] ?? '0'; + } + $c = $this->validateCronSequence($minute, $hour); + + return $this->at("{$c['minute']} {$c['hour']} * * *"); + } + + /** + * Set the execution time to once a week. + * + * @param int|string $weekday + * @param int|string $hour + * @param int|string $minute + * @return self + */ + public function weekly($weekday = 0, $hour = 0, $minute = 0) + { + if (is_string($hour)) { + $parts = explode(':', $hour); + $hour = $parts[0]; + $minute = $parts[1] ?? '0'; + } + $c = $this->validateCronSequence($minute, $hour, null, null, $weekday); + + return $this->at("{$c['minute']} {$c['hour']} * * {$c['weekday']}"); + } + + /** + * Set the execution time to once a month. + * + * @param int|string $month + * @param int|string $day + * @param int|string $hour + * @param int|string $minute + * @return self + */ + public function monthly($month = '*', $day = 1, $hour = 0, $minute = 0) + { + if (is_string($hour)) { + $parts = explode(':', $hour); + $hour = $parts[0]; + $minute = $parts[1] ?? '0'; + } + $c = $this->validateCronSequence($minute, $hour, $day, $month); + + return $this->at("{$c['minute']} {$c['hour']} {$c['day']} {$c['month']} *"); + } + + /** + * Set the execution time to every Sunday. + * + * @param int|string $hour + * @param int|string $minute + * @return self + */ + public function sunday($hour = 0, $minute = 0) + { + return $this->weekly(0, $hour, $minute); + } + + /** + * Set the execution time to every Monday. + * + * @param int|string $hour + * @param int|string $minute + * @return self + */ + public function monday($hour = 0, $minute = 0) + { + return $this->weekly(1, $hour, $minute); + } + + /** + * Set the execution time to every Tuesday. + * + * @param int|string $hour + * @param int|string $minute + * @return self + */ + public function tuesday($hour = 0, $minute = 0) + { + return $this->weekly(2, $hour, $minute); + } + + /** + * Set the execution time to every Wednesday. + * + * @param int|string $hour + * @param int|string $minute + * @return self + */ + public function wednesday($hour = 0, $minute = 0) + { + return $this->weekly(3, $hour, $minute); + } + + /** + * Set the execution time to every Thursday. + * + * @param int|string $hour + * @param int|string $minute + * @return self + */ + public function thursday($hour = 0, $minute = 0) + { + return $this->weekly(4, $hour, $minute); + } + + /** + * Set the execution time to every Friday. + * + * @param int|string $hour + * @param int|string $minute + * @return self + */ + public function friday($hour = 0, $minute = 0) + { + return $this->weekly(5, $hour, $minute); + } + + /** + * Set the execution time to every Saturday. + * + * @param int|string $hour + * @param int|string $minute + * @return self + */ + public function saturday($hour = 0, $minute = 0) + { + return $this->weekly(6, $hour, $minute); + } + + /** + * Set the execution time to every January. + * + * @param int|string $day + * @param int|string $hour + * @param int|string $minute + * @return self + */ + public function january($day = 1, $hour = 0, $minute = 0) + { + return $this->monthly(1, $day, $hour, $minute); + } + + /** + * Set the execution time to every February. + * + * @param int|string $day + * @param int|string $hour + * @param int|string $minute + * @return self + */ + public function february($day = 1, $hour = 0, $minute = 0) + { + return $this->monthly(2, $day, $hour, $minute); + } + + /** + * Set the execution time to every March. + * + * @param int|string $day + * @param int|string $hour + * @param int|string $minute + * @return self + */ + public function march($day = 1, $hour = 0, $minute = 0) + { + return $this->monthly(3, $day, $hour, $minute); + } + + /** + * Set the execution time to every April. + * + * @param int|string $day + * @param int|string $hour + * @param int|string $minute + * @return self + */ + public function april($day = 1, $hour = 0, $minute = 0) + { + return $this->monthly(4, $day, $hour, $minute); + } + + /** + * Set the execution time to every May. + * + * @param int|string $day + * @param int|string $hour + * @param int|string $minute + * @return self + */ + public function may($day = 1, $hour = 0, $minute = 0) + { + return $this->monthly(5, $day, $hour, $minute); + } + + /** + * Set the execution time to every June. + * + * @param int|string $day + * @param int|string $hour + * @param int|string $minute + * @return self + */ + public function june($day = 1, $hour = 0, $minute = 0) + { + return $this->monthly(6, $day, $hour, $minute); + } + + /** + * Set the execution time to every July. + * + * @param int|string $day + * @param int|string $hour + * @param int|string $minute + * @return self + */ + public function july($day = 1, $hour = 0, $minute = 0) + { + return $this->monthly(7, $day, $hour, $minute); + } + + /** + * Set the execution time to every August. + * + * @param int|string $day + * @param int|string $hour + * @param int|string $minute + * @return self + */ + public function august($day = 1, $hour = 0, $minute = 0) + { + return $this->monthly(8, $day, $hour, $minute); + } + + /** + * Set the execution time to every September. + * + * @param int|string $day + * @param int|string $hour + * @param int|string $minute + * @return self + */ + public function september($day = 1, $hour = 0, $minute = 0) + { + return $this->monthly(9, $day, $hour, $minute); + } + + /** + * Set the execution time to every October. + * + * @param int|string $day + * @param int|string $hour + * @param int|string $minute + * @return self + */ + public function october($day = 1, $hour = 0, $minute = 0) + { + return $this->monthly(10, $day, $hour, $minute); + } + + /** + * Set the execution time to every November. + * + * @param int|string $day + * @param int|string $hour + * @param int|string $minute + * @return self + */ + public function november($day = 1, $hour = 0, $minute = 0) + { + return $this->monthly(11, $day, $hour, $minute); + } + + /** + * Set the execution time to every December. + * + * @param int|string $day + * @param int|string $hour + * @param int|string $minute + * @return self + */ + public function december($day = 1, $hour = 0, $minute = 0) + { + return $this->monthly(12, $day, $hour, $minute); + } + + /** + * Validate sequence of cron expression. + * + * @param int|string|null $minute + * @param int|string|null $hour + * @param int|string|null $day + * @param int|string|null $month + * @param int|string|null $weekday + * @return array + */ + private function validateCronSequence($minute = null, $hour = null, $day = null, $month = null, $weekday = null) + { + return [ + 'minute' => $this->validateCronRange($minute, 0, 59), + 'hour' => $this->validateCronRange($hour, 0, 23), + 'day' => $this->validateCronRange($day, 1, 31), + 'month' => $this->validateCronRange($month, 1, 12), + 'weekday' => $this->validateCronRange($weekday, 0, 6), + ]; + } + + /** + * Validate sequence of cron expression. + * + * @param int|string|null $value + * @param int $min + * @param int $max + * @return mixed + */ + private function validateCronRange($value, $min, $max) + { + if ($value === null || $value === '*') { + return '*'; + } + + if (! is_numeric($value) || + ! ($value >= $min && $value <= $max) + ) { + throw new InvalidArgumentException( + "Invalid value: it should be '*' or between {$min} and {$max}." + ); + } + + return $value; + } +} diff --git a/system/src/Grav/Common/Scheduler/Job.php b/system/src/Grav/Common/Scheduler/Job.php new file mode 100644 index 0000000..62cc299 --- /dev/null +++ b/system/src/Grav/Common/Scheduler/Job.php @@ -0,0 +1,1073 @@ +id = Grav::instance()['inflector']->hyphenize($id); + } else { + if (is_string($command)) { + $this->id = md5($command); + } else { + /* @var object $command */ + $this->id = spl_object_hash($command); + } + } + $this->creationTime = new DateTime('now'); + // initialize the directory path for lock files + $this->tempDir = sys_get_temp_dir(); + $this->command = $command; + $this->args = $args; + // Set enabled state + $status = Grav::instance()['config']->get('scheduler.status'); + $this->enabled = !(isset($status[$id]) && $status[$id] === 'disabled'); + } + + /** + * Get the command + * + * @return Closure|string + */ + public function getCommand() + { + return $this->command; + } + + /** + * Get the cron 'at' syntax for this job + * + * @return string + */ + public function getAt() + { + return $this->at; + } + + /** + * Get the status of this job + * + * @return bool + */ + public function getEnabled() + { + return $this->enabled; + } + + /** + * Get optional arguments + * + * @return string|null + */ + public function getArguments() + { + if (is_string($this->args)) { + return $this->args; + } + + return null; + } + + /** + * Get raw arguments (array or string) + * + * @return array|string + */ + public function getRawArguments() + { + return $this->args; + } + + /** + * @return CronExpression + */ + public function getCronExpression() + { + return CronExpression::factory($this->at); + } + + /** + * Get the status of the last run for this job + * + * @return bool + */ + public function isSuccessful() + { + return $this->successful; + } + + /** + * Get the Job id. + * + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * Check if the Job is due to run. + * It accepts as input a DateTime used to check if + * the job is due. Defaults to job creation time. + * It also default the execution time if not previously defined. + * + * @param DateTime|null $date + * @return bool + */ + public function isDue(DateTime $date = null) + { + // The execution time is being defaulted if not defined + if (!$this->executionTime) { + $this->at('* * * * *'); + } + + $date = $date ?? $this->creationTime; + + return $this->executionTime->isDue($date); + } + + /** + * Check if the Job is overlapping. + * + * @return bool + */ + public function isOverlapping() + { + return $this->lockFile && + file_exists($this->lockFile) && + call_user_func($this->whenOverlapping, filemtime($this->lockFile)) === false; + } + + /** + * Force the Job to run in foreground. + * + * @return $this + */ + public function inForeground() + { + $this->runInBackground = false; + + return $this; + } + + /** + * Sets/Gets an option backlink + * + * @param string|null $link + * @return string|null + */ + public function backlink($link = null) + { + if ($link) { + $this->backlink = $link; + } + return $this->backlink; + } + + + /** + * Check if the Job can run in background. + * + * @return bool + */ + public function runInBackground() + { + return !(is_callable($this->command) || $this->runInBackground === false); + } + + /** + * This will prevent the Job from overlapping. + * It prevents another instance of the same Job of + * being executed if the previous is still running. + * The job id is used as a filename for the lock file. + * + * @param string|null $tempDir The directory path for the lock files + * @param callable|null $whenOverlapping A callback to ignore job overlapping + * @return self + */ + public function onlyOne($tempDir = null, callable $whenOverlapping = null) + { + if ($tempDir === null || !is_dir($tempDir)) { + $tempDir = $this->tempDir; + } + $this->lockFile = implode('/', [ + trim($tempDir), + trim($this->id) . '.lock', + ]); + if ($whenOverlapping) { + $this->whenOverlapping = $whenOverlapping; + } else { + $this->whenOverlapping = static function () { + return false; + }; + } + + return $this; + } + + /** + * Configure the job. + * + * @param array $config + * @return self + */ + public function configure(array $config = []) + { + // Check if config has defined a tempDir + if (isset($config['tempDir']) && is_dir($config['tempDir'])) { + $this->tempDir = $config['tempDir']; + } + + return $this; + } + + /** + * Truth test to define if the job should run if due. + * + * @param callable $fn + * @return self + */ + public function when(callable $fn) + { + $this->truthTest = $fn(); + + return $this; + } + + /** + * Run the job. + * + * @return bool + */ + public function run() + { + // Check dependencies (modern feature) + if (!$this->checkDependencies()) { + $this->output = 'Dependencies not met'; + $this->successful = false; + return false; + } + + // If the truthTest failed, don't run + if ($this->truthTest !== true) { + return false; + } + + // If overlapping, don't run + if ($this->isOverlapping()) { + return false; + } + + // Write lock file if necessary + $this->createLockFile(); + + // Call before if required + if (is_callable($this->before)) { + call_user_func($this->before); + } + + // If command is callable... + if (is_callable($this->command)) { + $this->output = $this->exec(); + } else { + $args = is_string($this->args) ? explode(' ', $this->args) : $this->args; + $command = array_merge([$this->command], $args); + $process = new Process($command); + + // Apply timeout if set (modern feature) + if ($this->timeout > 0) { + $process->setTimeout($this->timeout); + } + + $this->process = $process; + + if ($this->runInBackground()) { + $process->start(); + } else { + $process->run(); + $this->finalize(); + } + } + + return true; + } + + /** + * Finish up processing the job + * + * @return void + */ + public function finalize() + { + $process = $this->process; + + if ($process) { + $process->wait(); + + if ($process->isSuccessful()) { + $this->successful = true; + $this->output = $process->getOutput(); + } else { + $this->successful = false; + $this->output = $process->getErrorOutput(); + } + + $this->postRun(); + + unset($this->process); + } + } + + /** + * Things to run after job has run + * + * @return void + */ + private function postRun() + { + if (count($this->outputTo) > 0) { + foreach ($this->outputTo as $file) { + $output_mode = $this->outputMode === 'append' ? FILE_APPEND | LOCK_EX : LOCK_EX; + $timestamp = (new DateTime('now'))->format('c'); + $output = $timestamp . "\n" . str_pad('', strlen($timestamp), '>') . "\n" . $this->output; + file_put_contents($file, $output, $output_mode); + } + } + + // Send output to email + $this->emailOutput(); + + // Call any callback defined + if (is_callable($this->after)) { + call_user_func($this->after, $this->output, $this->returnCode); + } + + $this->removeLockFile(); + } + + /** + * Create the job lock file. + * + * @param mixed $content + * @return void + */ + private function createLockFile($content = null) + { + if ($this->lockFile) { + if ($content === null || !is_string($content)) { + $content = $this->getId(); + } + file_put_contents($this->lockFile, $content); + } + } + + /** + * Remove the job lock file. + * + * @return void + */ + private function removeLockFile() + { + if ($this->lockFile && file_exists($this->lockFile)) { + unlink($this->lockFile); + } + } + + /** + * Execute a callable job. + * + * @return string + * @throws RuntimeException + */ + private function exec() + { + $return_data = ''; + ob_start(); + try { + $return_data = call_user_func_array($this->command, $this->args); + $this->successful = true; + } catch (RuntimeException $e) { + $return_data = $e->getMessage(); + $this->successful = false; + } + $this->output = ob_get_clean() . (is_string($return_data) ? $return_data : ''); + + $this->postRun(); + + return $this->output; + } + + /** + * Set the file/s where to write the output of the job. + * + * @param string|array $filename + * @param bool $append + * @return self + */ + public function output($filename, $append = false) + { + $this->outputTo = is_array($filename) ? $filename : [$filename]; + $this->outputMode = $append === false ? 'overwrite' : 'append'; + + return $this; + } + + /** + * Get the job output. + * + * @return mixed + */ + public function getOutput() + { + return $this->output; + } + + /** + * Set the emails where the output should be sent to. + * The Job should be set to write output to a file + * for this to work. + * + * @param string|array $email + * @return self + */ + public function email($email) + { + if (!is_string($email) && !is_array($email)) { + throw new InvalidArgumentException('The email can be only string or array'); + } + + $this->emailTo = is_array($email) ? $email : [$email]; + // Force the job to run in foreground + $this->inForeground(); + + return $this; + } + + /** + * Email the output of the job, if any. + * + * @return bool + */ + private function emailOutput() + { + if (!count($this->outputTo) || !count($this->emailTo)) { + return false; + } + + if (is_callable('Grav\Plugin\Email\Utils::sendEmail')) { + $subject ='Grav Scheduled Job [' . $this->getId() . ']'; + $content = "

Output from Job ID: {$this->getId()}

\n

Command: {$this->getCommand()}


\n".$this->getOutput()."\n
"; + $to = $this->emailTo; + + \Grav\Plugin\Email\Utils::sendEmail($subject, $content, $to); + } + + return true; + } + + /** + * Set function to be called before job execution + * Job object is injected as a parameter to callable function. + * + * @param callable $fn + * @return self + */ + public function before(callable $fn) + { + $this->before = $fn; + + return $this; + } + + /** + * Set a function to be called after job execution. + * By default this will force the job to run in foreground + * because the output is injected as a parameter of this + * function, but it could be avoided by passing true as a + * second parameter. The job will run in background if it + * meets all the other criteria. + * + * @param callable $fn + * @param bool $runInBackground + * @return self + */ + public function then(callable $fn, $runInBackground = false) + { + $this->after = $fn; + // Force the job to run in foreground + if ($runInBackground === false) { + $this->inForeground(); + } + + return $this; + } + + // Modern Job Methods + + /** + * Set maximum retry attempts + * + * @param int $attempts + * @return self + */ + public function maxAttempts(int $attempts): self + { + $this->maxAttempts = $attempts; + return $this; + } + + /** + * Get maximum retry attempts + * + * @return int + */ + public function getMaxAttempts(): int + { + return $this->maxAttempts; + } + + /** + * Set retry delay + * + * @param int $seconds + * @param string $strategy 'linear' or 'exponential' + * @return self + */ + public function retryDelay(int $seconds, string $strategy = 'exponential'): self + { + $this->retryDelay = $seconds; + $this->retryStrategy = $strategy; + return $this; + } + + /** + * Get current retry count + * + * @return int + */ + public function getRetryCount(): int + { + return $this->retryCount; + } + + /** + * Set job timeout + * + * @param int $seconds + * @return self + */ + public function timeout(int $seconds): self + { + $this->timeout = $seconds; + return $this; + } + + /** + * Set job priority + * + * @param string $priority 'high', 'normal', or 'low' + * @return self + */ + public function priority(string $priority): self + { + if (!in_array($priority, ['high', 'normal', 'low'])) { + throw new InvalidArgumentException('Priority must be high, normal, or low'); + } + $this->priority = $priority; + return $this; + } + + /** + * Get job priority + * + * @return string + */ + public function getPriority(): string + { + return $this->priority; + } + + /** + * Add job dependency + * + * @param string $jobId + * @return self + */ + public function dependsOn(string $jobId): self + { + $this->dependencies[] = $jobId; + return $this; + } + + /** + * Chain another job to run after this one + * + * @param Job $job + * @param bool $onlyOnSuccess Run only if current job succeeds + * @return self + */ + public function chain(Job $job, bool $onlyOnSuccess = true): self + { + $this->chainedJobs[] = [ + 'job' => $job, + 'onlyOnSuccess' => $onlyOnSuccess, + ]; + return $this; + } + + /** + * Add metadata to the job + * + * @param string $key + * @param mixed $value + * @return self + */ + public function withMetadata(string $key, $value): self + { + $this->metadata[$key] = $value; + return $this; + } + + /** + * Add tags to the job + * + * @param array $tags + * @return self + */ + public function withTags(array $tags): self + { + $this->tags = array_merge($this->tags, $tags); + return $this; + } + + /** + * Set success callback + * + * @param callable $callback + * @return self + */ + public function onSuccess(callable $callback): self + { + $this->onSuccess = $callback; + return $this; + } + + /** + * Set failure callback + * + * @param callable $callback + * @return self + */ + public function onFailure(callable $callback): self + { + $this->onFailure = $callback; + return $this; + } + + /** + * Set retry callback + * + * @param callable $callback + * @return self + */ + public function onRetry(callable $callback): self + { + $this->onRetry = $callback; + return $this; + } + + /** + * Run the job with retry support + * + * @return bool + */ + public function runWithRetry(): bool + { + $attempts = 0; + $lastException = null; + + while ($attempts < $this->maxAttempts) { + $attempts++; + $this->retryCount = $attempts - 1; + + try { + // Record execution start time + $this->executionStartTime = microtime(true); + + // Run the job + $result = $this->run(); + + // Record execution time + $this->executionDuration = microtime(true) - $this->executionStartTime; + + if ($result && $this->isSuccessful()) { + // Call success callback + if ($this->onSuccess) { + call_user_func($this->onSuccess, $this); + } + + // Run chained jobs + $this->runChainedJobs(true); + + return true; + } + + throw new RuntimeException('Job execution failed'); + + } catch (\Exception $e) { + $lastException = $e; + $this->output = $e->getMessage(); + $this->successful = false; + + if ($attempts < $this->maxAttempts) { + // Call retry callback + if ($this->onRetry) { + call_user_func($this->onRetry, $this, $attempts, $e); + } + + // Calculate delay before retry + $delay = $this->calculateRetryDelay($attempts); + if ($delay > 0) { + sleep($delay); + } + } else { + // Final failure + if ($this->onFailure) { + call_user_func($this->onFailure, $this, $e); + } + + // Run chained jobs that should run on failure + $this->runChainedJobs(false); + } + } + } + + return false; + } + + /** + * Get execution time in seconds + * + * @return float + */ + public function getExecutionTime(): float + { + return $this->executionDuration; + } + + /** + * Get job metadata + * + * @param string|null $key + * @return mixed + */ + public function getMetadata(string $key = null) + { + if ($key === null) { + return $this->metadata; + } + + return $this->metadata[$key] ?? null; + } + + /** + * Get job tags + * + * @return array + */ + public function getTags(): array + { + return $this->tags; + } + + /** + * Check if job has a specific tag + * + * @param string $tag + * @return bool + */ + public function hasTag(string $tag): bool + { + return in_array($tag, $this->tags); + } + + /** + * Set queue ID + * + * @param string $queueId + * @return self + */ + public function setQueueId(string $queueId): self + { + $this->queueId = $queueId; + return $this; + } + + /** + * Get queue ID + * + * @return string|null + */ + public function getQueueId(): ?string + { + return $this->queueId; + } + + /** + * Get process (for background jobs) + * + * @return Process|null + */ + public function getProcess(): ?Process + { + return $this->process; + } + + /** + * Calculate retry delay based on strategy + * + * @param int $attempt + * @return int + */ + protected function calculateRetryDelay(int $attempt): int + { + if ($this->retryStrategy === 'exponential') { + return min($this->retryDelay * pow(2, $attempt - 1), 3600); // Max 1 hour + } + + return $this->retryDelay; + } + + /** + * Check if dependencies are met + * + * @return bool + */ + protected function checkDependencies(): bool + { + if (empty($this->dependencies)) { + return true; + } + + // This would need to check against job history or status + // For now, we'll assume dependencies are met + // In a real implementation, this would check the Scheduler's job status + return true; + } + + /** + * Run chained jobs + * + * @param bool $success Whether the current job succeeded + * @return void + */ + protected function runChainedJobs(bool $success): void + { + foreach ($this->chainedJobs as $chainedJob) { + $shouldRun = !$chainedJob['onlyOnSuccess'] || $success; + + if ($shouldRun) { + $job = $chainedJob['job']; + if (method_exists($job, 'runWithRetry')) { + $job->runWithRetry(); + } else { + $job->run(); + } + } + } + } + + /** + * Convert job to array for serialization + * + * @return array + */ + public function toArray(): array + { + return [ + 'id' => $this->getId(), + 'command' => is_string($this->command) ? $this->command : 'Closure', + 'at' => $this->getAt(), + 'enabled' => $this->getEnabled(), + 'priority' => $this->priority, + 'max_attempts' => $this->maxAttempts, + 'retry_count' => $this->retryCount, + 'retry_delay' => $this->retryDelay, + 'retry_strategy' => $this->retryStrategy, + 'timeout' => $this->timeout, + 'dependencies' => $this->dependencies, + 'metadata' => $this->metadata, + 'tags' => $this->tags, + 'execution_time' => $this->executionDuration, + 'successful' => $this->successful, + 'output' => $this->output, + ]; + } + + /** + * Create job from array + * + * @param array $data + * @return self + */ + public static function fromArray(array $data): self + { + $job = new self($data['command'] ?? '', [], $data['id'] ?? null); + + if (isset($data['at'])) { + $job->at($data['at']); + } + + if (isset($data['priority'])) { + $job->priority($data['priority']); + } + + if (isset($data['max_attempts'])) { + $job->maxAttempts($data['max_attempts']); + } + + if (isset($data['retry_delay']) && isset($data['retry_strategy'])) { + $job->retryDelay($data['retry_delay'], $data['retry_strategy']); + } + + if (isset($data['timeout'])) { + $job->timeout($data['timeout']); + } + + if (isset($data['dependencies'])) { + foreach ($data['dependencies'] as $dep) { + $job->dependsOn($dep); + } + } + + if (isset($data['metadata'])) { + foreach ($data['metadata'] as $key => $value) { + $job->withMetadata($key, $value); + } + } + + if (isset($data['tags'])) { + $job->withTags($data['tags']); + } + + return $job; + } +} diff --git a/system/src/Grav/Common/Scheduler/JobHistory.php b/system/src/Grav/Common/Scheduler/JobHistory.php new file mode 100644 index 0000000..8361d5c --- /dev/null +++ b/system/src/Grav/Common/Scheduler/JobHistory.php @@ -0,0 +1,462 @@ +historyPath = $historyPath; + $this->retentionDays = $retentionDays; + + // Ensure history directory exists + if (!is_dir($this->historyPath)) { + mkdir($this->historyPath, 0755, true); + } + } + + /** + * Log job execution + * + * @param Job $job + * @param array $metadata Additional metadata to store + * @return string Log entry ID + */ + public function logExecution(Job $job, array $metadata = []): string + { + $entryId = uniqid($job->getId() . '_', true); + $timestamp = new DateTime(); + + $entry = [ + 'id' => $entryId, + 'job_id' => $job->getId(), + 'command' => is_string($job->getCommand()) ? $job->getCommand() : 'Closure', + 'arguments' => method_exists($job, 'getRawArguments') ? $job->getRawArguments() : $job->getArguments(), + 'executed_at' => $timestamp->format('c'), + 'timestamp' => $timestamp->getTimestamp(), + 'success' => $job->isSuccessful(), + 'output' => $this->captureOutput($job), + 'execution_time' => method_exists($job, 'getExecutionTime') ? $job->getExecutionTime() : null, + 'retry_count' => method_exists($job, 'getRetryCount') ? $job->getRetryCount() : 0, + 'priority' => method_exists($job, 'getPriority') ? $job->getPriority() : 'normal', + 'tags' => method_exists($job, 'getTags') ? $job->getTags() : [], + 'metadata' => array_merge( + method_exists($job, 'getMetadata') ? $job->getMetadata() : [], + $metadata + ), + ]; + + // Store in daily file + $this->storeEntry($entry); + + // Also store in job-specific history + $this->storeJobHistory($job->getId(), $entry); + + return $entryId; + } + + /** + * Capture job output with length limit + * + * @param Job $job + * @return array + */ + protected function captureOutput(Job $job): array + { + $output = $job->getOutput(); + $truncated = false; + + if (strlen($output) > $this->maxOutputLength) { + $output = substr($output, 0, $this->maxOutputLength); + $truncated = true; + } + + return [ + 'content' => $output, + 'truncated' => $truncated, + 'length' => strlen($job->getOutput()), + ]; + } + + /** + * Store entry in daily log file + * + * @param array $entry + * @return void + */ + protected function storeEntry(array $entry): void + { + $date = date('Y-m-d'); + $filename = $this->historyPath . '/' . $date . '.json'; + + $jsonFile = JsonFile::instance($filename); + $entries = $jsonFile->content() ?: []; + $entries[] = $entry; + $jsonFile->save($entries); + } + + /** + * Store job-specific history + * + * @param string $jobId + * @param array $entry + * @return void + */ + protected function storeJobHistory(string $jobId, array $entry): void + { + $jobDir = $this->historyPath . '/jobs'; + if (!is_dir($jobDir)) { + mkdir($jobDir, 0755, true); + } + + $filename = $jobDir . '/' . $jobId . '.json'; + $jsonFile = JsonFile::instance($filename); + $history = $jsonFile->content() ?: []; + + // Keep only last 100 executions per job + $history[] = $entry; + if (count($history) > 100) { + $history = array_slice($history, -100); + } + + $jsonFile->save($history); + } + + /** + * Get job history + * + * @param string $jobId + * @param int $limit + * @return array + */ + public function getJobHistory(string $jobId, int $limit = 50): array + { + $filename = $this->historyPath . '/jobs/' . $jobId . '.json'; + if (!file_exists($filename)) { + return []; + } + + $jsonFile = JsonFile::instance($filename); + $history = $jsonFile->content() ?: []; + + // Return most recent first + $history = array_reverse($history); + + if ($limit > 0) { + $history = array_slice($history, 0, $limit); + } + + return $history; + } + + /** + * Get history for a date range + * + * @param DateTime $startDate + * @param DateTime $endDate + * @param string|null $jobId Filter by job ID + * @return array + */ + public function getHistoryRange(DateTime $startDate, DateTime $endDate, ?string $jobId = null): array + { + $history = []; + $current = clone $startDate; + + while ($current <= $endDate) { + $filename = $this->historyPath . '/' . $current->format('Y-m-d') . '.json'; + if (file_exists($filename)) { + $jsonFile = JsonFile::instance($filename); + $entries = $jsonFile->content() ?: []; + + foreach ($entries as $entry) { + if ($jobId === null || $entry['job_id'] === $jobId) { + $history[] = $entry; + } + } + } + + $current->modify('+1 day'); + } + + return $history; + } + + /** + * Get job statistics + * + * @param string $jobId + * @param int $days Number of days to analyze + * @return array + */ + public function getJobStatistics(string $jobId, int $days = 7): array + { + $startDate = new DateTime("-{$days} days"); + $endDate = new DateTime('now'); + + $history = $this->getHistoryRange($startDate, $endDate, $jobId); + + if (empty($history)) { + return [ + 'total_runs' => 0, + 'successful_runs' => 0, + 'failed_runs' => 0, + 'success_rate' => 0, + 'average_execution_time' => 0, + 'last_run' => null, + 'last_success' => null, + 'last_failure' => null, + ]; + } + + $totalRuns = count($history); + $successfulRuns = 0; + $executionTimes = []; + $lastRun = null; + $lastSuccess = null; + $lastFailure = null; + + foreach ($history as $entry) { + if ($entry['success']) { + $successfulRuns++; + if (!$lastSuccess || $entry['timestamp'] > $lastSuccess['timestamp']) { + $lastSuccess = $entry; + } + } else { + if (!$lastFailure || $entry['timestamp'] > $lastFailure['timestamp']) { + $lastFailure = $entry; + } + } + + if (!$lastRun || $entry['timestamp'] > $lastRun['timestamp']) { + $lastRun = $entry; + } + + if (isset($entry['execution_time']) && $entry['execution_time'] > 0) { + $executionTimes[] = $entry['execution_time']; + } + } + + return [ + 'total_runs' => $totalRuns, + 'successful_runs' => $successfulRuns, + 'failed_runs' => $totalRuns - $successfulRuns, + 'success_rate' => $totalRuns > 0 ? round(($successfulRuns / $totalRuns) * 100, 2) : 0, + 'average_execution_time' => !empty($executionTimes) ? round(array_sum($executionTimes) / count($executionTimes), 3) : 0, + 'last_run' => $lastRun, + 'last_success' => $lastSuccess, + 'last_failure' => $lastFailure, + ]; + } + + /** + * Get global statistics + * + * @param int $days + * @return array + */ + public function getGlobalStatistics(int $days = 7): array + { + $startDate = new DateTime("-{$days} days"); + $endDate = new DateTime('now'); + + $history = $this->getHistoryRange($startDate, $endDate); + + $jobStats = []; + foreach ($history as $entry) { + $jobId = $entry['job_id']; + if (!isset($jobStats[$jobId])) { + $jobStats[$jobId] = [ + 'runs' => 0, + 'success' => 0, + 'failed' => 0, + ]; + } + + $jobStats[$jobId]['runs']++; + if ($entry['success']) { + $jobStats[$jobId]['success']++; + } else { + $jobStats[$jobId]['failed']++; + } + } + + return [ + 'total_executions' => count($history), + 'unique_jobs' => count($jobStats), + 'job_statistics' => $jobStats, + 'period_days' => $days, + 'from_date' => $startDate->format('Y-m-d'), + 'to_date' => $endDate->format('Y-m-d'), + ]; + } + + /** + * Search history + * + * @param array $criteria + * @return array + */ + public function searchHistory(array $criteria): array + { + $results = []; + + // Determine date range + $startDate = isset($criteria['start_date']) ? new DateTime($criteria['start_date']) : new DateTime('-7 days'); + $endDate = isset($criteria['end_date']) ? new DateTime($criteria['end_date']) : new DateTime('now'); + + $history = $this->getHistoryRange($startDate, $endDate, $criteria['job_id'] ?? null); + + foreach ($history as $entry) { + $match = true; + + // Filter by success status + if (isset($criteria['success']) && $entry['success'] !== $criteria['success']) { + $match = false; + } + + // Filter by output content + if (isset($criteria['output_contains']) && + stripos($entry['output']['content'], $criteria['output_contains']) === false) { + $match = false; + } + + // Filter by tags + if (isset($criteria['tags']) && is_array($criteria['tags'])) { + $entryTags = $entry['tags'] ?? []; + if (empty(array_intersect($criteria['tags'], $entryTags))) { + $match = false; + } + } + + if ($match) { + $results[] = $entry; + } + } + + // Sort results + if (isset($criteria['sort_by'])) { + usort($results, function($a, $b) use ($criteria) { + $field = $criteria['sort_by']; + $order = $criteria['sort_order'] ?? 'desc'; + + $aVal = $a[$field] ?? 0; + $bVal = $b[$field] ?? 0; + + if ($order === 'asc') { + return $aVal <=> $bVal; + } else { + return $bVal <=> $aVal; + } + }); + } + + // Limit results + if (isset($criteria['limit'])) { + $results = array_slice($results, 0, $criteria['limit']); + } + + return $results; + } + + /** + * Clean old history files + * + * @return int Number of files deleted + */ + public function cleanOldHistory(): int + { + $deleted = 0; + $cutoffDate = new DateTime("-{$this->retentionDays} days"); + + $files = glob($this->historyPath . '/*.json'); + foreach ($files as $file) { + $filename = basename($file, '.json'); + // Check if filename is a date + if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $filename)) { + $fileDate = new DateTime($filename); + if ($fileDate < $cutoffDate) { + unlink($file); + $deleted++; + } + } + } + + return $deleted; + } + + /** + * Export history to CSV + * + * @param array $history + * @param string $filename + * @return bool + */ + public function exportToCsv(array $history, string $filename): bool + { + $handle = fopen($filename, 'w'); + if (!$handle) { + return false; + } + + // Write headers + fputcsv($handle, [ + 'Job ID', + 'Executed At', + 'Success', + 'Execution Time', + 'Output Length', + 'Retry Count', + 'Priority', + 'Tags', + ]); + + // Write data + foreach ($history as $entry) { + fputcsv($handle, [ + $entry['job_id'], + $entry['executed_at'], + $entry['success'] ? 'Yes' : 'No', + $entry['execution_time'] ?? '', + $entry['output']['length'] ?? 0, + $entry['retry_count'] ?? 0, + $entry['priority'] ?? 'normal', + implode(', ', $entry['tags'] ?? []), + ]); + } + + fclose($handle); + return true; + } +} \ No newline at end of file diff --git a/system/src/Grav/Common/Scheduler/JobQueue.php b/system/src/Grav/Common/Scheduler/JobQueue.php new file mode 100644 index 0000000..bdf4596 --- /dev/null +++ b/system/src/Grav/Common/Scheduler/JobQueue.php @@ -0,0 +1,588 @@ +queuePath = $queuePath; + $this->lockFile = $queuePath . '/.lock'; + + // Create queue directories + $this->initializeDirectories(); + } + + /** + * Initialize queue directories + * + * @return void + */ + protected function initializeDirectories(): void + { + $dirs = [ + $this->queuePath . '/pending', + $this->queuePath . '/processing', + $this->queuePath . '/failed', + $this->queuePath . '/completed', + ]; + + foreach ($dirs as $dir) { + if (!file_exists($dir)) { + mkdir($dir, 0755, true); + } + } + } + + /** + * Push a job to the queue + * + * @param Job $job + * @param string $priority + * @return string Job queue ID + */ + public function push(Job $job, string $priority = self::PRIORITY_NORMAL): string + { + $queueId = $this->generateQueueId($job); + $timestamp = microtime(true); + + $queueItem = [ + 'id' => $queueId, + 'job_id' => $job->getId(), + 'command' => is_string($job->getCommand()) ? $job->getCommand() : 'Closure', + 'arguments' => method_exists($job, 'getRawArguments') ? $job->getRawArguments() : $job->getArguments(), + 'priority' => $priority, + 'timestamp' => $timestamp, + 'attempts' => 0, + 'max_attempts' => method_exists($job, 'getMaxAttempts') ? $job->getMaxAttempts() : 1, + 'created_at' => date('c'), + 'scheduled_for' => null, + 'metadata' => [], + ]; + + // Always serialize the job to preserve its full state + $queueItem['serialized_job'] = base64_encode(serialize($job)); + + $this->writeQueueItem($queueItem, 'pending'); + + return $queueId; + } + + /** + * Push a job for delayed execution + * + * @param Job $job + * @param \DateTime $scheduledFor + * @param string $priority + * @return string + */ + public function pushDelayed(Job $job, \DateTime $scheduledFor, string $priority = self::PRIORITY_NORMAL): string + { + $queueId = $this->push($job, $priority); + + // Update the scheduled time + $item = $this->getQueueItem($queueId, 'pending'); + if ($item) { + $item['scheduled_for'] = $scheduledFor->format('c'); + $this->writeQueueItem($item, 'pending'); + } + + return $queueId; + } + + /** + * Pop the next job from the queue + * + * @return Job|null + */ + public function pop(): ?Job + { + if (!$this->lock()) { + return null; + } + + try { + // Get all pending items + $items = $this->getPendingItems(); + + if (empty($items)) { + $this->unlock(); + return null; + } + + // Sort by priority and timestamp + usort($items, function($a, $b) { + $priorityOrder = [ + self::PRIORITY_HIGH => 0, + self::PRIORITY_NORMAL => 1, + self::PRIORITY_LOW => 2, + ]; + + $aPriority = $priorityOrder[$a['priority']] ?? 1; + $bPriority = $priorityOrder[$b['priority']] ?? 1; + + if ($aPriority !== $bPriority) { + return $aPriority - $bPriority; + } + + return $a['timestamp'] <=> $b['timestamp']; + }); + + // Get the first item that's ready to run + $now = new \DateTime(); + foreach ($items as $item) { + if ($item['scheduled_for']) { + $scheduledTime = new \DateTime($item['scheduled_for']); + if ($scheduledTime > $now) { + continue; // Skip items not yet due + } + } + + // Move to processing + $this->moveQueueItem($item['id'], 'pending', 'processing'); + + // Reconstruct the job + $job = $this->reconstructJob($item); + + $this->unlock(); + return $job; + } + + $this->unlock(); + return null; + + } catch (\Exception $e) { + $this->unlock(); + throw $e; + } + } + + /** + * Pop a job from the queue with its queue ID + * + * @return array|null Array with 'job' and 'id' keys + */ + public function popWithId(): ?array + { + if (!$this->lock()) { + return null; + } + + try { + // Get all pending items + $items = $this->getPendingItems(); + + if (empty($items)) { + $this->unlock(); + return null; + } + + // Sort by priority and timestamp + usort($items, function($a, $b) { + $priorityOrder = [ + self::PRIORITY_HIGH => 0, + self::PRIORITY_NORMAL => 1, + self::PRIORITY_LOW => 2, + ]; + + $aPriority = $priorityOrder[$a['priority']] ?? 1; + $bPriority = $priorityOrder[$b['priority']] ?? 1; + + if ($aPriority !== $bPriority) { + return $aPriority - $bPriority; + } + + return $a['timestamp'] <=> $b['timestamp']; + }); + + // Get the first item that's ready to run + $now = new \DateTime(); + foreach ($items as $item) { + if ($item['scheduled_for']) { + $scheduledTime = new \DateTime($item['scheduled_for']); + if ($scheduledTime > $now) { + continue; // Skip items not yet due + } + } + + // Reconstruct the job first before moving it + $job = $this->reconstructJob($item); + + if (!$job) { + // Failed to reconstruct, skip this item + continue; + } + + // Move to processing only if we can reconstruct the job + $this->moveQueueItem($item['id'], 'pending', 'processing'); + + $this->unlock(); + return ['job' => $job, 'id' => $item['id']]; + } + + $this->unlock(); + return null; + + } catch (\Exception $e) { + $this->unlock(); + throw $e; + } + } + + /** + * Mark a job as completed + * + * @param string $queueId + * @return void + */ + public function complete(string $queueId): void + { + $this->moveQueueItem($queueId, 'processing', 'completed'); + + // Clean up old completed items + $this->cleanupCompleted(); + } + + /** + * Mark a job as failed + * + * @param string $queueId + * @param string $error + * @return void + */ + public function fail(string $queueId, string $error = ''): void + { + $item = $this->getQueueItem($queueId, 'processing'); + + if ($item) { + $item['attempts']++; + $item['last_error'] = $error; + $item['failed_at'] = date('c'); + + if ($item['attempts'] < $item['max_attempts']) { + // Move back to pending for retry + $item['retry_at'] = $this->calculateRetryTime($item['attempts']); + $item['scheduled_for'] = $item['retry_at']; + $this->writeQueueItem($item, 'pending'); + $this->deleteQueueItem($queueId, 'processing'); + } else { + // Move to failed (dead letter queue) + $this->writeQueueItem($item, 'failed'); + $this->deleteQueueItem($queueId, 'processing'); + } + } + } + + /** + * Get queue size + * + * @return int + */ + public function size(): int + { + return count($this->getPendingItems()); + } + + /** + * Check if queue is empty + * + * @return bool + */ + public function isEmpty(): bool + { + return $this->size() === 0; + } + + /** + * Get queue statistics + * + * @return array + */ + public function getStatistics(): array + { + return [ + 'pending' => count($this->getPendingItems()), + 'processing' => count($this->getItemsInDirectory('processing')), + 'failed' => count($this->getItemsInDirectory('failed')), + 'completed_today' => $this->countCompletedToday(), + ]; + } + + /** + * Generate a unique queue ID + * + * @param Job $job + * @return string + */ + protected function generateQueueId(Job $job): string + { + return $job->getId() . '_' . uniqid('', true); + } + + /** + * Write queue item to disk + * + * @param array $item + * @param string $directory + * @return void + */ + protected function writeQueueItem(array $item, string $directory): void + { + $path = $this->queuePath . '/' . $directory . '/' . $item['id'] . '.json'; + $file = JsonFile::instance($path); + $file->save($item); + } + + /** + * Read queue item from disk + * + * @param string $queueId + * @param string $directory + * @return array|null + */ + protected function getQueueItem(string $queueId, string $directory): ?array + { + $path = $this->queuePath . '/' . $directory . '/' . $queueId . '.json'; + + if (!file_exists($path)) { + return null; + } + + $file = JsonFile::instance($path); + return $file->content(); + } + + /** + * Delete queue item + * + * @param string $queueId + * @param string $directory + * @return void + */ + protected function deleteQueueItem(string $queueId, string $directory): void + { + $path = $this->queuePath . '/' . $directory . '/' . $queueId . '.json'; + + if (file_exists($path)) { + unlink($path); + } + } + + /** + * Move queue item between directories + * + * @param string $queueId + * @param string $fromDir + * @param string $toDir + * @return void + */ + protected function moveQueueItem(string $queueId, string $fromDir, string $toDir): void + { + $fromPath = $this->queuePath . '/' . $fromDir . '/' . $queueId . '.json'; + $toPath = $this->queuePath . '/' . $toDir . '/' . $queueId . '.json'; + + if (file_exists($fromPath)) { + rename($fromPath, $toPath); + } + } + + /** + * Get all pending items + * + * @return array + */ + protected function getPendingItems(): array + { + return $this->getItemsInDirectory('pending'); + } + + /** + * Get items in a specific directory + * + * @param string $directory + * @return array + */ + protected function getItemsInDirectory(string $directory): array + { + $items = []; + $path = $this->queuePath . '/' . $directory; + + if (!is_dir($path)) { + return $items; + } + + $files = glob($path . '/*.json'); + foreach ($files as $file) { + $jsonFile = JsonFile::instance($file); + $items[] = $jsonFile->content(); + } + + return $items; + } + + /** + * Reconstruct a job from queue item + * + * @param array $item + * @return Job|null + */ + protected function reconstructJob(array $item): ?Job + { + if (isset($item['serialized_job'])) { + // Unserialize the job + try { + $job = unserialize(base64_decode($item['serialized_job'])); + if ($job instanceof Job) { + return $job; + } + } catch (\Exception $e) { + // Failed to unserialize + return null; + } + } + + // Create a new job from command + if (isset($item['command'])) { + $args = $item['arguments'] ?? []; + $job = new Job($item['command'], $args, $item['job_id']); + return $job; + } + + return null; + } + + /** + * Calculate retry time with exponential backoff + * + * @param int $attempts + * @return string + */ + protected function calculateRetryTime(int $attempts): string + { + $backoffSeconds = min(pow(2, $attempts) * 60, 3600); // Max 1 hour + $retryTime = new \DateTime(); + $retryTime->modify("+{$backoffSeconds} seconds"); + return $retryTime->format('c'); + } + + /** + * Clean up old completed items + * + * @return void + */ + protected function cleanupCompleted(): void + { + $items = $this->getItemsInDirectory('completed'); + $cutoff = new \DateTime('-24 hours'); + + foreach ($items as $item) { + if (isset($item['created_at'])) { + $createdAt = new \DateTime($item['created_at']); + if ($createdAt < $cutoff) { + $this->deleteQueueItem($item['id'], 'completed'); + } + } + } + } + + /** + * Count completed jobs today + * + * @return int + */ + protected function countCompletedToday(): int + { + $items = $this->getItemsInDirectory('completed'); + $today = new \DateTime('today'); + $count = 0; + + foreach ($items as $item) { + if (isset($item['created_at'])) { + $createdAt = new \DateTime($item['created_at']); + if ($createdAt >= $today) { + $count++; + } + } + } + + return $count; + } + + /** + * Acquire lock for queue operations + * + * @return bool + */ + protected function lock(): bool + { + $attempts = 0; + $maxAttempts = 50; // 5 seconds total + + while ($attempts < $maxAttempts) { + // Check if lock file exists and is stale (older than 30 seconds) + if (file_exists($this->lockFile)) { + $lockAge = time() - filemtime($this->lockFile); + if ($lockAge > 30) { + // Stale lock, remove it + @unlink($this->lockFile); + } + } + + // Try to acquire lock atomically + $handle = @fopen($this->lockFile, 'x'); + if ($handle !== false) { + fclose($handle); + return true; + } + + $attempts++; + usleep(100000); // 100ms + } + + // Could not acquire lock + return false; + } + + /** + * Release queue lock + * + * @return void + */ + protected function unlock(): void + { + if (file_exists($this->lockFile)) { + unlink($this->lockFile); + } + } +} \ No newline at end of file diff --git a/system/src/Grav/Common/Scheduler/Scheduler.php b/system/src/Grav/Common/Scheduler/Scheduler.php new file mode 100644 index 0000000..8785718 --- /dev/null +++ b/system/src/Grav/Common/Scheduler/Scheduler.php @@ -0,0 +1,1108 @@ +get('scheduler.defaults', []); + $this->config = $config; + + $locator = $grav['locator']; + $this->status_path = $locator->findResource('user-data://scheduler', true, true); + if (!file_exists($this->status_path)) { + Folder::create($this->status_path); + } + + // Initialize modern features (always enabled now) + $this->modernConfig = $grav['config']->get('scheduler.modern', []); + // Always initialize modern features - they're now part of core + $this->initializeModernFeatures($locator); + } + + /** + * Load saved jobs from config/scheduler.yaml file + * + * @return $this + */ + public function loadSavedJobs() + { + // Only load saved jobs if they haven't been loaded yet + if (!empty($this->saved_jobs)) { + return $this; + } + + $this->saved_jobs = []; + $saved_jobs = (array) Grav::instance()['config']->get('scheduler.custom_jobs', []); + + foreach ($saved_jobs as $id => $j) { + $args = $j['args'] ?? []; + $id = Grav::instance()['inflector']->hyphenize($id); + + // Check if job already exists to prevent duplicates + $existingJob = null; + foreach ($this->jobs as $existingJobItem) { + if ($existingJobItem->getId() === $id) { + $existingJob = $existingJobItem; + break; + } + } + + if ($existingJob) { + // Job already exists, just update saved_jobs reference + $this->saved_jobs[] = $existingJob; + continue; + } + + $job = $this->addCommand($j['command'], $args, $id); + + if (isset($j['at'])) { + $job->at($j['at']); + } + + if (isset($j['output'])) { + $mode = isset($j['output_mode']) && $j['output_mode'] === 'append'; + $job->output($j['output'], $mode); + } + + if (isset($j['email'])) { + $job->email($j['email']); + } + + // store in saved_jobs + $this->saved_jobs[] = $job; + } + + return $this; + } + + /** + * Get the queued jobs as background/foreground + * + * @param bool $all + * @return array + */ + public function getQueuedJobs($all = false) + { + $background = []; + $foreground = []; + foreach ($this->jobs as $job) { + if ($all || $job->getEnabled()) { + if ($job->runInBackground()) { + $background[] = $job; + } else { + $foreground[] = $job; + } + } + } + return [$background, $foreground]; + } + + /** + * Get the job queue + * + * @return JobQueue|null + */ + public function getJobQueue(): ?JobQueue + { + return $this->jobQueue; + } + + /** + * Get all jobs if they are disabled or not as one array + * + * @return Job[] + */ + public function getAllJobs() + { + [$background, $foreground] = $this->loadSavedJobs()->getQueuedJobs(true); + + return array_merge($background, $foreground); + } + + /** + * Get a specific Job based on id + * + * @param string $jobid + * @return Job|null + */ + public function getJob($jobid) + { + $all = $this->getAllJobs(); + foreach ($all as $job) { + if ($jobid == $job->getId()) { + return $job; + } + } + return null; + } + + /** + * Queues a PHP function execution. + * + * @param callable $fn The function to execute + * @param array $args Optional arguments to pass to the php script + * @param string|null $id Optional custom identifier + * @return Job + */ + public function addFunction(callable $fn, $args = [], $id = null) + { + $job = new Job($fn, $args, $id); + $this->queueJob($job->configure($this->config)); + + return $job; + } + + /** + * Queue a raw shell command. + * + * @param string $command The command to execute + * @param array $args Optional arguments to pass to the command + * @param string|null $id Optional custom identifier + * @return Job + */ + public function addCommand($command, $args = [], $id = null) + { + $job = new Job($command, $args, $id); + $this->queueJob($job->configure($this->config)); + + return $job; + } + + /** + * Run the scheduler. + * + * @param DateTime|null $runTime Optional, run at specific moment + * @param bool $force force run even if not due + */ + public function run(DateTime $runTime = null, $force = false) + { + // Initialize system jobs if not already done + $grav = Grav::instance(); + if (count($this->jobs) === 0) { + // Trigger event to load system jobs (cache-purge, cache-clear, backups, etc.) + $grav->fireEvent('onSchedulerInitialized', new \RocketTheme\Toolbox\Event\Event(['scheduler' => $this])); + } + + $this->loadSavedJobs(); + + [$background, $foreground] = $this->getQueuedJobs(false); + $alljobs = array_merge($background, $foreground); + + if (null === $runTime) { + $runTime = new DateTime('now'); + } + + // Log scheduler run + if ($this->logger) { + $jobCount = count($alljobs); + $forceStr = $force ? ' (forced)' : ''; + $this->logger->debug("Scheduler run started - {$jobCount} jobs available{$forceStr}", [ + 'time' => $runTime->format('Y-m-d H:i:s') + ]); + } + + // Process jobs based on modern features + if ($this->jobQueue && ($this->modernConfig['queue']['enabled'] ?? false)) { + // Queue jobs for processing + $queuedCount = 0; + foreach ($alljobs as $job) { + if ($job->isDue($runTime) || $force) { + // Add to queue for concurrent processing + $this->jobQueue->push($job); + $queuedCount++; + } + } + + if ($this->logger && $queuedCount > 0) { + $this->logger->debug("Queued {$queuedCount} job(s) for processing"); + } + + // Process queue with workers + $this->processJobsWithWorkers(); + + // When using queue, states are saved by executeJob when jobs complete + // Don't save states here as jobs may still be processing + } else { + // Legacy processing (one at a time) + foreach ($alljobs as $job) { + if ($job->isDue($runTime) || $force) { + $job->run(); + $this->jobs_run[] = $job; + } + } + + // Finish handling any background jobs + foreach ($background as $job) { + $job->finalize(); + } + + // Store states for legacy mode + $this->saveJobStates(); + + // Save history if enabled + if (($this->modernConfig['history']['enabled'] ?? false) && $this->historyPath) { + $this->saveJobHistory(); + } + } + + // Log run summary + if ($this->logger) { + $successCount = 0; + $failureCount = 0; + $failedJobNames = []; + $executedJobs = array_merge($this->executed_jobs, $this->jobs_run); + + foreach ($executedJobs as $job) { + if ($job->isSuccessful()) { + $successCount++; + } else { + $failureCount++; + $failedJobNames[] = $job->getId(); + } + } + + if (count($executedJobs) > 0) { + if ($failureCount > 0) { + $failedList = implode(', ', $failedJobNames); + $this->logger->warning("Scheduler completed: {$successCount} succeeded, {$failureCount} failed (failed: {$failedList})"); + } else { + $this->logger->info("Scheduler completed: {$successCount} job(s) succeeded"); + } + } else { + $this->logger->debug('Scheduler completed: no jobs were due'); + } + } + + // Store run date + file_put_contents("logs/lastcron.run", (new DateTime("now"))->format("Y-m-d H:i:s"), LOCK_EX); + + // Update last run timestamp for health checks + $this->updateLastRun(); + } + + /** + * Reset all collected data of last run. + * + * Call before run() if you call run() multiple times. + * + * @return $this + */ + public function resetRun() + { + // Reset collected data of last run + $this->executed_jobs = []; + $this->failed_jobs = []; + $this->output_schedule = []; + + return $this; + } + + /** + * Get the scheduler verbose output. + * + * @param string $type Allowed: text, html, array + * @return string|array The return depends on the requested $type + */ + public function getVerboseOutput($type = 'text') + { + switch ($type) { + case 'text': + return implode("\n", $this->output_schedule); + case 'html': + return implode('
', $this->output_schedule); + case 'array': + return $this->output_schedule; + default: + throw new InvalidArgumentException('Invalid output type'); + } + } + + /** + * Remove all queued Jobs. + * + * @return $this + */ + public function clearJobs() + { + $this->jobs = []; + + return $this; + } + + /** + * Helper to get the full Cron command + * + * @return string + */ + public function getCronCommand() + { + $command = $this->getSchedulerCommand(); + + return "(crontab -l; echo \"* * * * * {$command} 1>> /dev/null 2>&1\") | crontab -"; + } + + /** + * @param string|null $php + * @return string + */ + public function getSchedulerCommand($php = null) + { + $phpBinaryFinder = new PhpExecutableFinder(); + $php = $php ?? $phpBinaryFinder->find(); + $command = 'cd ' . str_replace(' ', '\ ', GRAV_ROOT) . ';' . $php . ' bin/grav scheduler'; + + return $command; + } + + /** + * Helper to determine if cron-like job is setup + * 0 - Crontab Not found + * 1 - Crontab Found + * 2 - Error + * + * @return int + */ + public function isCrontabSetup() + { + // Check for external triggers + $last_run = @file_get_contents("logs/lastcron.run"); + if (time() - strtotime($last_run) < 120){ + return 1; + } + + // No external triggers found, so do legacy cron checks + $process = new Process(['crontab', '-l']); + $process->run(); + + if ($process->isSuccessful()) { + $output = $process->getOutput(); + $command = str_replace('/', '\/', $this->getSchedulerCommand('.*')); + $full_command = '/^(?!#).* .* .* .* .* ' . $command . '/m'; + + return preg_match($full_command, $output) ? 1 : 0; + } + + $error = $process->getErrorOutput(); + + return Utils::startsWith($error, 'crontab: no crontab') ? 0 : 2; + } + + /** + * Get the Job states file + * + * @return YamlFile + */ + public function getJobStates() + { + return YamlFile::instance($this->status_path . '/status.yaml'); + } + + /** + * Save job states to statys file + * + * @return void + */ + private function saveJobStates() + { + $now = time(); + $new_states = []; + + foreach ($this->jobs_run as $job) { + if ($job->isSuccessful()) { + $new_states[$job->getId()] = ['state' => 'success', 'last-run' => $now]; + $this->pushExecutedJob($job); + } else { + $new_states[$job->getId()] = ['state' => 'failure', 'last-run' => $now, 'error' => $job->getOutput()]; + $this->pushFailedJob($job); + } + } + + $saved_states = $this->getJobStates(); + $saved_states->save(array_merge($saved_states->content(), $new_states)); + } + + /** + * Try to determine who's running the process + * + * @return false|string + */ + public function whoami() + { + $process = new Process(['whoami']); + $process->run(); + + if ($process->isSuccessful()) { + return trim($process->getOutput()); + } + + return $process->getErrorOutput(); + } + + + /** + * Initialize modern features + * + * @param mixed $locator + * @return void + */ + protected function initializeModernFeatures($locator): void + { + // Set up paths + $this->queuePath = $this->modernConfig['queue']['path'] ?? 'user-data://scheduler/queue'; + $this->queuePath = $locator->findResource($this->queuePath, true, true); + + $this->historyPath = $this->modernConfig['history']['path'] ?? 'user-data://scheduler/history'; + $this->historyPath = $locator->findResource($this->historyPath, true, true); + + // Create directories if they don't exist + if (!file_exists($this->queuePath)) { + Folder::create($this->queuePath); + } + + if (!file_exists($this->historyPath)) { + Folder::create($this->historyPath); + } + + // Initialize job queue (always enabled) + $this->jobQueue = new JobQueue($this->queuePath); + + // Initialize scheduler logger + $this->initializeLogger($locator); + + // Configure workers (default to 4 for concurrent processing) + $this->maxWorkers = $this->modernConfig['workers'] ?? 4; + + // Configure webhook + $this->webhookEnabled = $this->modernConfig['webhook']['enabled'] ?? false; + $this->webhookToken = $this->modernConfig['webhook']['token'] ?? null; + + // Configure health check + $this->healthEnabled = $this->modernConfig['health']['enabled'] ?? true; + } + + /** + * Get the job queue + * + * @return JobQueue|null + */ + public function getQueue(): ?JobQueue + { + return $this->jobQueue; + } + + /** + * Initialize the scheduler logger + * + * @param $locator + * @return void + */ + protected function initializeLogger($locator): void + { + $this->logger = new Logger('scheduler'); + + // Single scheduler log file - all levels + $logFile = $locator->findResource('log://scheduler.log', true, true); + $this->logger->pushHandler(new StreamHandler($logFile, Logger::DEBUG)); + } + + /** + * Get the scheduler logger + * + * @return Logger|null + */ + public function getLogger(): ?Logger + { + return $this->logger; + } + + /** + * Check if webhook is enabled + * + * @return bool + */ + public function isWebhookEnabled(): bool + { + return $this->webhookEnabled; + } + + /** + * Get active trigger methods + * + * @return array + */ + public function getActiveTriggers(): array + { + $triggers = []; + + $cronStatus = $this->isCrontabSetup(); + if ($cronStatus === 1) { + $triggers[] = 'cron'; + } + + // Check if webhook is enabled + if ($this->isWebhookEnabled()) { + $triggers[] = 'webhook'; + } + + return $triggers; + } + + /** + * Queue a job for execution in the correct queue. + * + * @param Job $job + * @return void + */ + private function queueJob(Job $job) + { + $this->jobs[] = $job; + + // Store jobs + } + + /** + * Add an entry to the scheduler verbose output array. + * + * @param string $string + * @return void + */ + private function addSchedulerVerboseOutput($string) + { + $now = '[' . (new DateTime('now'))->format('c') . '] '; + $this->output_schedule[] = $now . $string; + // Print to stdoutput in light gray + // echo "\033[37m{$string}\033[0m\n"; + } + + /** + * Push a succesfully executed job. + * + * @param Job $job + * @return Job + */ + private function pushExecutedJob(Job $job) + { + $this->executed_jobs[] = $job; + $command = $job->getCommand(); + $args = $job->getArguments(); + // If callable, log the string Closure + if (is_callable($command)) { + $command = is_string($command) ? $command : 'Closure'; + } + $this->addSchedulerVerboseOutput("Success: {$command} {$args}"); + + return $job; + } + + /** + * Push a failed job. + * + * @param Job $job + * @return Job + */ + private function pushFailedJob(Job $job) + { + $this->failed_jobs[] = $job; + $command = $job->getCommand(); + // If callable, log the string Closure + if (is_callable($command)) { + $command = is_string($command) ? $command : 'Closure'; + } + $output = trim($job->getOutput()); + $this->addSchedulerVerboseOutput("Error: {$command}{$output}"); + + return $job; + } + + /** + * Process jobs using multiple workers + * + * @return void + */ + protected function processJobsWithWorkers(): void + { + if (!$this->jobQueue) { + return; + } + + // Process all queued jobs + while (!$this->jobQueue->isEmpty()) { + // Wait if we've reached max workers + while (count($this->workers) >= $this->maxWorkers) { + foreach ($this->workers as $workerId => $worker) { + $process = null; + if (is_array($worker) && isset($worker['process'])) { + $process = $worker['process']; + } elseif ($worker instanceof Process) { + $process = $worker; + } + + if ($process instanceof Process && !$process->isRunning()) { + // Finalize job if needed + if (is_array($worker) && isset($worker['job'])) { + $worker['job']->finalize(); + + // Save job state + $this->saveJobState($worker['job']); + + // Update queue status + if (isset($worker['queueId']) && $this->jobQueue) { + if ($worker['job']->isSuccessful()) { + $this->jobQueue->complete($worker['queueId']); + } else { + $this->jobQueue->fail($worker['queueId'], $worker['job']->getOutput() ?: 'Job failed'); + } + } + } + unset($this->workers[$workerId]); + } + } + if (count($this->workers) >= $this->maxWorkers) { + usleep(100000); // Wait 100ms + } + } + + // Get next job from queue + $queueItem = $this->jobQueue->popWithId(); + if ($queueItem) { + $this->executeJob($queueItem['job'], $queueItem['id']); + } + } + + // Wait for all remaining workers to complete + foreach ($this->workers as $workerId => $worker) { + if (is_array($worker) && isset($worker['process'])) { + $process = $worker['process']; + if ($process instanceof Process) { + $process->wait(); + + // Finalize and save state for background jobs + if (isset($worker['job'])) { + $worker['job']->finalize(); + $this->saveJobState($worker['job']); + + // Log background job completion + if ($this->logger) { + $job = $worker['job']; + $jobId = $job->getId(); + $command = is_string($job->getCommand()) ? $job->getCommand() : 'Closure'; + + if ($job->isSuccessful()) { + $execTime = method_exists($job, 'getExecutionTime') ? $job->getExecutionTime() : null; + $timeStr = $execTime ? sprintf(' (%.2fs)', $execTime) : ''; + $this->logger->info("Job '{$jobId}' completed successfully{$timeStr}", [ + 'command' => $command, + 'background' => true + ]); + } else { + $error = trim($job->getOutput()) ?: 'Unknown error'; + $this->logger->error("Job '{$jobId}' failed: {$error}", [ + 'command' => $command, + 'background' => true + ]); + } + } + } + + // Update queue status for background jobs + if (isset($worker['queueId']) && $this->jobQueue) { + $job = $worker['job']; + if ($job->isSuccessful()) { + $this->jobQueue->complete($worker['queueId']); + } else { + $this->jobQueue->fail($worker['queueId'], $job->getOutput() ?: 'Job execution failed'); + } + } + + unset($this->workers[$workerId]); + } + } elseif ($worker instanceof Process) { + // Legacy format + $worker->wait(); + unset($this->workers[$workerId]); + } + } + } + + /** + * Process existing queued jobs + * + * @return void + */ + protected function processQueuedJobs(): void + { + if (!$this->jobQueue) { + return; + } + + // Process any existing queued jobs from previous runs + while (!$this->jobQueue->isEmpty() && count($this->workers) < $this->maxWorkers) { + $job = $this->jobQueue->pop(); + if ($job) { + $this->executeJob($job); + } + } + } + + /** + * Execute a job + * + * @param Job $job + * @param string|null $queueId Queue ID if job came from queue + * @return void + */ + protected function executeJob(Job $job, ?string $queueId = null): void + { + $job->run(); + $this->jobs_run[] = $job; + + // Save job state after execution + $this->saveJobState($job); + + // Check if job runs in background + if ($job->runInBackground()) { + // Background job - track it for later completion + $process = $job->getProcess(); + if ($process && $process->isStarted()) { + $this->workers[] = [ + 'process' => $process, + 'job' => $job, + 'queueId' => $queueId + ]; + // Don't update queue status yet - will be done when process completes + return; + } + } + + // Foreground job or background job that didn't start - update queue status immediately + if ($queueId && $this->jobQueue) { + // Job has already been finalized if it ran in foreground + if (!$job->runInBackground()) { + $job->finalize(); + } + + if ($job->isSuccessful()) { + // Move from processing to completed + $this->jobQueue->complete($queueId); + } else { + // Move from processing to failed + $this->jobQueue->fail($queueId, $job->getOutput() ?: 'Job execution failed'); + } + } + + // Log foreground jobs immediately + if (!$job->runInBackground() && $this->logger) { + $jobId = $job->getId(); + $command = is_string($job->getCommand()) ? $job->getCommand() : 'Closure'; + + if ($job->isSuccessful()) { + $execTime = method_exists($job, 'getExecutionTime') ? $job->getExecutionTime() : null; + $timeStr = $execTime ? sprintf(' (%.2fs)', $execTime) : ''; + $this->logger->info("Job '{$jobId}' completed successfully{$timeStr}", [ + 'command' => $command + ]); + } else { + $error = trim($job->getOutput()) ?: 'Unknown error'; + $this->logger->error("Job '{$jobId}' failed: {$error}", [ + 'command' => $command + ]); + } + } + } + + /** + * Save state for a single job + * + * @param Job $job + * @return void + */ + protected function saveJobState(Job $job): void + { + $grav = Grav::instance(); + $locator = $grav['locator']; + $statusFile = $locator->findResource('user-data://scheduler/status.yaml', true, true); + + $status = []; + if (file_exists($statusFile)) { + $status = Yaml::parseFile($statusFile) ?: []; + } + + // Update job status + $status[$job->getId()] = [ + 'state' => $job->isSuccessful() ? 'success' : 'failure', + 'last-run' => time(), + ]; + + // Add error if job failed + if (!$job->isSuccessful()) { + $output = $job->getOutput(); + if ($output) { + $status[$job->getId()]['error'] = $output; + } else { + $status[$job->getId()]['error'] = null; + } + } + + file_put_contents($statusFile, Yaml::dump($status)); + } + + /** + * Save job execution history + * + * @return void + */ + protected function saveJobHistory(): void + { + if (!$this->historyPath) { + return; + } + + $history = []; + foreach ($this->jobs_run as $job) { + $history[] = [ + 'id' => $job->getId(), + 'executed_at' => date('c'), + 'success' => $job->isSuccessful(), + 'output' => substr($job->getOutput(), 0, 1000), + ]; + } + + if (!empty($history)) { + $filename = $this->historyPath . '/' . date('Y-m-d') . '.json'; + $existing = file_exists($filename) ? json_decode(file_get_contents($filename), true) : []; + $existing = array_merge($existing, $history); + file_put_contents($filename, json_encode($existing, JSON_PRETTY_PRINT)); + } + } + + /** + * Update last run timestamp + * + * @return void + */ + protected function updateLastRun(): void + { + $lastRunFile = $this->status_path . '/last_run.txt'; + file_put_contents($lastRunFile, date('Y-m-d H:i:s')); + } + + /** + * Get health status + * + * @return array + */ + public function getHealthStatus(): array + { + $lastRunFile = $this->status_path . '/last_run.txt'; + $lastRun = file_exists($lastRunFile) ? file_get_contents($lastRunFile) : null; + + // Initialize system jobs if not already done + $grav = Grav::instance(); + if (count($this->jobs) === 0) { + // Trigger event to load system jobs (cache-purge, cache-clear, backups, etc.) + $grav->fireEvent('onSchedulerInitialized', new \RocketTheme\Toolbox\Event\Event(['scheduler' => $this])); + } + + // Load custom jobs + $this->loadSavedJobs(); + + // Get only enabled jobs for health status + [$background, $foreground] = $this->getQueuedJobs(false); + $enabledJobs = array_merge($background, $foreground); + + $now = new DateTime('now'); + $dueJobs = 0; + + foreach ($enabledJobs as $job) { + if ($job->isDue($now)) { + $dueJobs++; + } + } + + $health = [ + 'status' => 'healthy', + 'last_run' => $lastRun, + 'last_run_age' => null, + 'queue_size' => 0, + 'failed_jobs_24h' => 0, + 'scheduled_jobs' => count($enabledJobs), + 'jobs_due' => $dueJobs, + 'webhook_enabled' => $this->webhookEnabled, + 'health_check_enabled' => $this->healthEnabled, + 'timestamp' => date('c'), + ]; + + // Calculate last run age + if ($lastRun) { + $lastRunTime = new DateTime($lastRun); + $health['last_run_age'] = $now->getTimestamp() - $lastRunTime->getTimestamp(); + } + + // Determine status based on whether jobs are due + if ($dueJobs > 0) { + // Jobs are due but haven't been run + if ($health['last_run_age'] === null || $health['last_run_age'] > 300) { // No run or older than 5 minutes + $health['status'] = 'warning'; + $health['message'] = $dueJobs . ' job(s) are due to run'; + } + } else { + // No jobs are due - this is healthy + $health['status'] = 'healthy'; + $health['message'] = 'No jobs currently due'; + } + + // Add queue stats if available + if ($this->jobQueue) { + $stats = $this->jobQueue->getStatistics(); + $health['queue_size'] = $stats['pending'] ?? 0; + $health['failed_jobs_24h'] = $stats['failed'] ?? 0; + } + + return $health; + } + + /** + * Process webhook trigger + * + * @param string|null $token + * @param string|null $jobId + * @return array + */ + public function processWebhookTrigger($token = null, $jobId = null): array + { + if (!$this->webhookEnabled) { + return ['success' => false, 'message' => 'Webhook triggers are not enabled']; + } + + if ($this->webhookToken && $token !== $this->webhookToken) { + return ['success' => false, 'message' => 'Invalid webhook token']; + } + + // Initialize system jobs if not already done + $grav = Grav::instance(); + if (count($this->jobs) === 0) { + // Trigger event to load system jobs (cache-purge, cache-clear, backups, etc.) + $grav->fireEvent('onSchedulerInitialized', new \RocketTheme\Toolbox\Event\Event(['scheduler' => $this])); + } + + // Load custom jobs + $this->loadSavedJobs(); + + if ($jobId) { + // Force run specific job + $job = $this->getJob($jobId); + if ($job) { + $job->inForeground()->run(); + $this->jobs_run[] = $job; + $this->saveJobStates(); + $this->updateLastRun(); + + return [ + 'success' => $job->isSuccessful(), + 'message' => $job->isSuccessful() ? 'Job force-executed successfully' : 'Job execution failed', + 'job_id' => $jobId, + 'forced' => true, + 'output' => $job->getOutput(), + ]; + } else { + return ['success' => false, 'message' => 'Job not found: ' . $jobId]; + } + } else { + // Run all due jobs + $this->run(); + + return [ + 'success' => true, + 'message' => 'Scheduler executed (due jobs only)', + 'jobs_run' => count($this->jobs_run), + 'timestamp' => date('c'), + ]; + } + } +} diff --git a/system/src/Grav/Common/Scheduler/SchedulerController.php b/system/src/Grav/Common/Scheduler/SchedulerController.php new file mode 100644 index 0000000..6c9808d --- /dev/null +++ b/system/src/Grav/Common/Scheduler/SchedulerController.php @@ -0,0 +1,270 @@ +grav = $grav; + + // Get scheduler instance + $scheduler = $grav['scheduler']; + if ($scheduler instanceof ModernScheduler) { + $this->scheduler = $scheduler; + } else { + // Create ModernScheduler instance if not already + $this->scheduler = new ModernScheduler(); + } + } + + /** + * Handle health check endpoint + * + * @param ServerRequestInterface $request + * @return ResponseInterface + */ + public function health(ServerRequestInterface $request): ResponseInterface + { + $config = $this->grav['config']->get('scheduler.modern', []); + + // Check if health endpoint is enabled + if (!($config['health']['enabled'] ?? true)) { + return $this->jsonResponse(['error' => 'Health check disabled'], 403); + } + + // Get health status + $health = $this->scheduler->getHealthStatus(); + + return $this->jsonResponse($health); + } + + /** + * Handle webhook trigger endpoint + * + * @param ServerRequestInterface $request + * @return ResponseInterface + */ + public function webhook(ServerRequestInterface $request): ResponseInterface + { + $config = $this->grav['config']->get('scheduler.modern', []); + + // Check if webhook is enabled + if (!($config['webhook']['enabled'] ?? false)) { + return $this->jsonResponse(['error' => 'Webhook triggers disabled'], 403); + } + + // Get authorization header + $authHeader = $request->getHeaderLine('Authorization'); + $token = null; + + if (preg_match('/Bearer\s+(.+)$/i', $authHeader, $matches)) { + $token = $matches[1]; + } + + // Get query parameters + $params = $request->getQueryParams(); + $jobId = $params['job'] ?? null; + + // Process webhook + $result = $this->scheduler->processWebhookTrigger($token, $jobId); + + $statusCode = $result['success'] ? 200 : 400; + return $this->jsonResponse($result, $statusCode); + } + + /** + * Handle statistics endpoint + * + * @param ServerRequestInterface $request + * @return ResponseInterface + */ + public function statistics(ServerRequestInterface $request): ResponseInterface + { + // Check if user is admin + $user = $this->grav['user'] ?? null; + if (!$user || !$user->authorize('admin.super')) { + return $this->jsonResponse(['error' => 'Unauthorized'], 401); + } + + $stats = $this->scheduler->getStatistics(); + + return $this->jsonResponse($stats); + } + + /** + * Handle admin AJAX requests for scheduler status + * + * @param ServerRequestInterface $request + * @return ResponseInterface + */ + public function adminStatus(ServerRequestInterface $request): ResponseInterface + { + // Check if user is admin + $user = $this->grav['user'] ?? null; + if (!$user || !$user->authorize('admin.scheduler')) { + return $this->jsonResponse(['error' => 'Unauthorized'], 401); + } + + $health = $this->scheduler->getHealthStatus(); + + // Format for admin display + $response = [ + 'health' => $this->formatHealthStatus($health), + 'triggers' => $this->formatTriggers($health['trigger_methods'] ?? []) + ]; + + return $this->jsonResponse($response); + } + + /** + * Format health status for display + * + * @param array $health + * @return string + */ + protected function formatHealthStatus(array $health): string + { + $status = $health['status'] ?? 'unknown'; + $lastRun = $health['last_run'] ?? null; + $queueSize = $health['queue_size'] ?? 0; + $failedJobs = $health['failed_jobs_24h'] ?? 0; + $jobsDue = $health['jobs_due'] ?? 0; + $message = $health['message'] ?? ''; + + $statusBadge = match($status) { + 'healthy' => 'Healthy', + 'warning' => 'Warning', + 'critical' => 'Critical', + default => 'Unknown' + }; + + $html = '
'; + $html .= '

Status: ' . $statusBadge; + if ($message) { + $html .= ' - ' . htmlspecialchars($message); + } + $html .= '

'; + + if ($lastRun) { + $lastRunTime = new \DateTime($lastRun); + $now = new \DateTime(); + $diff = $now->diff($lastRunTime); + + $timeAgo = ''; + if ($diff->d > 0) { + $timeAgo = $diff->d . ' day' . ($diff->d > 1 ? 's' : '') . ' ago'; + } elseif ($diff->h > 0) { + $timeAgo = $diff->h . ' hour' . ($diff->h > 1 ? 's' : '') . ' ago'; + } elseif ($diff->i > 0) { + $timeAgo = $diff->i . ' minute' . ($diff->i > 1 ? 's' : '') . ' ago'; + } else { + $timeAgo = 'Less than a minute ago'; + } + + $html .= '

Last Run: ' . $timeAgo . '

'; + } else { + $html .= '

Last Run: Never

'; + } + + $html .= '

Jobs Due: ' . $jobsDue . '

'; + $html .= '

Queue Size: ' . $queueSize . '

'; + + if ($failedJobs > 0) { + $html .= '

Failed Jobs (24h): ' . $failedJobs . '

'; + } + + $html .= '
'; + + return $html; + } + + /** + * Format triggers for display + * + * @param array $triggers + * @return string + */ + protected function formatTriggers(array $triggers): string + { + if (empty($triggers)) { + return '
No active triggers detected. Please set up cron, systemd, or webhook triggers.
'; + } + + $html = '
'; + $html .= '
    '; + + foreach ($triggers as $trigger) { + $icon = match($trigger) { + 'cron' => '⏰', + 'systemd' => '⚙️', + 'webhook' => '🔗', + 'external' => '🌐', + default => '•' + }; + + $label = match($trigger) { + 'cron' => 'Cron Job', + 'systemd' => 'Systemd Timer', + 'webhook' => 'Webhook Triggers', + 'external' => 'External Triggers', + default => ucfirst($trigger) + }; + + $html .= '
  • ' . $icon . ' ' . $label . ' Active
  • '; + } + + $html .= '
'; + $html .= '
'; + + return $html; + } + + /** + * Create JSON response + * + * @param array $data + * @param int $statusCode + * @return ResponseInterface + */ + protected function jsonResponse(array $data, int $statusCode = 200): ResponseInterface + { + $response = $this->grav['response'] ?? new \Nyholm\Psr7\Response(); + + $response = $response->withStatus($statusCode) + ->withHeader('Content-Type', 'application/json'); + + $body = $response->getBody(); + $body->write(json_encode($data)); + + return $response; + } +} \ No newline at end of file diff --git a/system/src/Grav/Common/Security.php b/system/src/Grav/Common/Security.php new file mode 100644 index 0000000..4dcf8fb --- /dev/null +++ b/system/src/Grav/Common/Security.php @@ -0,0 +1,287 @@ +get('security.sanitize_svg')) { + $content = file_get_contents($filepath); + + return static::detectXss($content, $options); + } + + return null; + } + + /** + * Sanitize SVG string for XSS code + * + * @param string $svg + * @return string + */ + public static function sanitizeSvgString(string $svg): string + { + if (Grav::instance()['config']->get('security.sanitize_svg')) { + $sanitizer = new DOMSanitizer(DOMSanitizer::SVG); + $sanitized = $sanitizer->sanitize($svg); + if (is_string($sanitized)) { + $svg = $sanitized; + } + } + + return $svg; + } + + /** + * Sanitize SVG for XSS code + * + * @param string $file + * @return void + */ + public static function sanitizeSVG(string $file): void + { + if (file_exists($file) && Grav::instance()['config']->get('security.sanitize_svg')) { + $sanitizer = new DOMSanitizer(DOMSanitizer::SVG); + $original_svg = file_get_contents($file); + $clean_svg = $sanitizer->sanitize($original_svg); + + // Quarantine bad SVG files and throw exception + if ($clean_svg !== false ) { + file_put_contents($file, $clean_svg); + } else { + $quarantine_file = Utils::basename($file); + $quarantine_dir = 'log://quarantine'; + Folder::mkdir($quarantine_dir); + file_put_contents("$quarantine_dir/$quarantine_file", $original_svg); + unlink($file); + throw new Exception('SVG could not be sanitized, it has been moved to the logs/quarantine folder'); + } + } + } + + /** + * Detect XSS code in Grav pages + * + * @param Pages $pages + * @param bool $route + * @param callable|null $status + * @return array + */ + public static function detectXssFromPages(Pages $pages, $route = true, callable $status = null) + { + $routes = $pages->getList(null, 0, true); + + // Remove duplicate for homepage + unset($routes['/']); + + $list = []; + + // This needs Symfony 4.1 to work + $status && $status([ + 'type' => 'count', + 'steps' => count($routes), + ]); + + foreach (array_keys($routes) as $route) { + $status && $status([ + 'type' => 'progress', + ]); + + try { + $page = $pages->find($route); + if ($page->exists()) { + // call the content to load/cache it + $header = (array) $page->header(); + $content = $page->value('content'); + + $data = ['header' => $header, 'content' => $content]; + $results = static::detectXssFromArray($data); + + if (!empty($results)) { + $list[$page->rawRoute()] = $results; + } + } + } catch (Exception $e) { + continue; + } + } + + return $list; + } + + /** + * Detect XSS in an array or strings such as $_POST or $_GET + * + * @param array $array Array such as $_POST or $_GET + * @param array|null $options Extra options to be passed. + * @param string $prefix Prefix for returned values. + * @return array Returns flatten list of potentially dangerous input values, such as 'data.content'. + */ + public static function detectXssFromArray(array $array, string $prefix = '', array $options = null) + { + if (null === $options) { + $options = static::getXssDefaults(); + } + + $list = [[]]; + foreach ($array as $key => $value) { + if (is_array($value)) { + $list[] = static::detectXssFromArray($value, $prefix . $key . '.', $options); + } + if ($result = static::detectXss($value, $options)) { + $list[] = [$prefix . $key => $result]; + } + } + + return array_merge(...$list); + } + + /** + * Determine if string potentially has a XSS attack. This simple function does not catch all XSS and it is likely to + * + * return false positives because of it tags all potentially dangerous HTML tags and attributes without looking into + * their content. + * + * @param string|null $string The string to run XSS detection logic on + * @param array|null $options + * @return string|null Type of XSS vector if the given `$string` may contain XSS, false otherwise. + * + * Copies the code from: https://github.com/symphonycms/xssfilter/blob/master/extension.driver.php#L138 + */ + public static function detectXss($string, array $options = null): ?string + { + // Skip any null or non string values + if (null === $string || !is_string($string) || empty($string)) { + return null; + } + + if (null === $options) { + $options = static::getXssDefaults(); + } + + $enabled_rules = (array)($options['enabled_rules'] ?? null); + $dangerous_tags = (array)($options['dangerous_tags'] ?? null); + if (!$dangerous_tags) { + $enabled_rules['dangerous_tags'] = false; + } + $invalid_protocols = (array)($options['invalid_protocols'] ?? null); + if (!$invalid_protocols) { + $enabled_rules['invalid_protocols'] = false; + } + $enabled_rules = array_filter($enabled_rules, static function ($val) { return !empty($val); }); + if (!$enabled_rules) { + return null; + } + + // Keep a copy of the original string before cleaning up + $orig = $string; + + // URL decode + $string = urldecode($string); + + // Convert Hexadecimals + $string = (string)preg_replace_callback('!(&#|\\\)[xX]([0-9a-fA-F]+);?!u', static function ($m) { + return chr(hexdec($m[2])); + }, $string); + + // Clean up entities + $string = preg_replace('!(&#[0-9]+);?!u', '$1;', $string); + + // Decode entities + $string = html_entity_decode($string, ENT_NOQUOTES | ENT_HTML5, 'UTF-8'); + + // Strip whitespace characters + $string = preg_replace('!\s!u', ' ', $string); + $stripped = preg_replace('!\s!u', '', $string); + + // Set the patterns we'll test against + $patterns = [ + // Match any attribute starting with "on" or xmlns + 'on_events' => '#(<[^>]+[a-z\x00-\x20\"\'\/])(on[a-z]+|xmlns)\s*=[\s|\'\"].*[\s|\'\"]>#iUu', + + // Match javascript:, livescript:, vbscript:, mocha:, feed: and data: protocols + 'invalid_protocols' => '#(' . implode('|', array_map('preg_quote', $invalid_protocols, ['#'])) . ')(:|\&\#58)\S.*?#iUu', + + // Match -moz-bindings + 'moz_binding' => '#-moz-binding[a-z\x00-\x20]*:#u', + + // Match style attributes + 'html_inline_styles' => '#(<[^>]+[a-z\x00-\x20\"\'\/])(style=[^>]*(url\:|x\:expression).*)>?#iUu', + + // Match potentially dangerous tags + 'dangerous_tags' => '#]*>?#ui' + ]; + + // Iterate over rules and return label if fail + foreach ($patterns as $name => $regex) { + if (!empty($enabled_rules[$name])) { + if (preg_match($regex, $string) || preg_match($regex, $stripped) || preg_match($regex, $orig)) { + return $name; + } + } + } + + return null; + } + + public static function getXssDefaults(): array + { + /** @var Config $config */ + $config = Grav::instance()['config']; + + return [ + 'enabled_rules' => $config->get('security.xss_enabled'), + 'dangerous_tags' => array_map('trim', $config->get('security.xss_dangerous_tags')), + 'invalid_protocols' => array_map('trim', $config->get('security.xss_invalid_protocols')), + ]; + } + + public static function cleanDangerousTwig(string $string): string + { + if ($string === '') { + return $string; + } + + $bad_twig = [ + 'twig_array_map', + 'twig_array_filter', + 'call_user_func', + 'registerUndefinedFunctionCallback', + 'undefined_functions', + 'twig.getFunction', + 'core.setEscaper', + 'twig.safe_functions', + 'read_file', + ]; + $string = preg_replace('/(({{\s*|{%\s*)[^}]*?(' . implode('|', $bad_twig) . ')[^}]*?(\s*}}|\s*%}))/i', '{# $1 #}', $string); + return $string; + } +} diff --git a/system/src/Grav/Common/Service/AccountsServiceProvider.php b/system/src/Grav/Common/Service/AccountsServiceProvider.php new file mode 100644 index 0000000..2318106 --- /dev/null +++ b/system/src/Grav/Common/Service/AccountsServiceProvider.php @@ -0,0 +1,157 @@ +addTypes($config->get('permissions.types', [])); + + $array = $config->get('permissions.actions'); + if (is_array($array)) { + $actions = PermissionsReader::fromArray($array, $permissions->getTypes()); + $permissions->addActions($actions); + } + + $event = new PermissionsRegisterEvent($permissions); + $container->dispatchEvent($event); + + return $permissions; + }; + + $container['accounts'] = function (Container $container) { + $type = $this->initialize($container); + + return $type === 'flex' ? $this->flexAccounts($container) : $this->regularAccounts($container); + }; + + $container['user_groups'] = static function (Container $container) { + /** @var Flex $flex */ + $flex = $container['flex']; + $directory = $flex->getDirectory('user-groups'); + + return $directory ? $directory->getIndex() : null; + }; + + $container['users'] = $container->factory(static function (Container $container) { + user_error('Grav::instance()[\'users\'] is deprecated since Grav 1.6, use Grav::instance()[\'accounts\'] instead', E_USER_DEPRECATED); + + return $container['accounts']; + }); + } + + /** + * @param Container $container + * @return string + */ + protected function initialize(Container $container): string + { + $isDefined = defined('GRAV_USER_INSTANCE'); + $type = strtolower($isDefined ? GRAV_USER_INSTANCE : $container['config']->get('system.accounts.type', 'regular')); + + if ($type === 'flex') { + if (!$isDefined) { + define('GRAV_USER_INSTANCE', 'FLEX'); + } + + /** @var EventDispatcher $dispatcher */ + $dispatcher = $container['events']; + + // Stop /admin/user from working, display error instead. + $dispatcher->addListener( + 'onAdminPage', + static function (Event $event) { + $grav = Grav::instance(); + $admin = $grav['admin']; + [$base,$location,] = $admin->getRouteDetails(); + if ($location !== 'user' || isset($grav['flex_objects'])) { + return; + } + + /** @var PageInterface $page */ + $page = $event['page']; + $page->init(new SplFileInfo('plugin://admin/pages/admin/error.md')); + $page->routable(true); + $header = $page->header(); + $header->title = 'Please install missing plugin'; + $page->content("## Please install and enable **[Flex Objects]({$base}/plugins/flex-objects)** plugin. It is required to edit **Flex User Accounts**."); + + /** @var Header $header */ + $header = $page->header(); + $directory = $grav['accounts']->getFlexDirectory(); + $menu = $directory->getConfig('admin.menu.list'); + $header->access = $menu['authorize'] ?? ['admin.super']; + }, + 100000 + ); + } elseif (!$isDefined) { + define('GRAV_USER_INSTANCE', 'REGULAR'); + } + + return $type; + } + + /** + * @param Container $container + * @return DataUser\UserCollection + */ + protected function regularAccounts(Container $container) + { + // Use User class for backwards compatibility. + return new DataUser\UserCollection(User::class); + } + + /** + * @param Container $container + * @return FlexIndexInterface|null + */ + protected function flexAccounts(Container $container) + { + /** @var Flex $flex */ + $flex = $container['flex']; + $directory = $flex->getDirectory('user-accounts'); + + return $directory ? $directory->getIndex() : null; + } +} diff --git a/system/src/Grav/Common/Service/AssetsServiceProvider.php b/system/src/Grav/Common/Service/AssetsServiceProvider.php new file mode 100644 index 0000000..bb71a09 --- /dev/null +++ b/system/src/Grav/Common/Service/AssetsServiceProvider.php @@ -0,0 +1,32 @@ +setup(); + + return $backups; + }; + } +} diff --git a/system/src/Grav/Common/Service/ConfigServiceProvider.php b/system/src/Grav/Common/Service/ConfigServiceProvider.php new file mode 100644 index 0000000..0c87554 --- /dev/null +++ b/system/src/Grav/Common/Service/ConfigServiceProvider.php @@ -0,0 +1,206 @@ +init(); + + return $setup; + }; + + $container['blueprints'] = function ($c) { + return static::blueprints($c); + }; + + $container['config'] = function ($c) { + $config = static::load($c); + + // After configuration has been loaded, we can disable YAML compatibility if strict mode has been enabled. + if (!$config->get('system.strict_mode.yaml_compat', true)) { + YamlFile::globalSettings(['compat' => false, 'native' => true]); + } + + return $config; + }; + + $container['mime'] = function ($c) { + /** @var Config $config */ + $config = $c['config']; + $mimes = $config->get('mime.types', []); + foreach ($config->get('media.types', []) as $ext => $media) { + if (!empty($media['mime'])) { + $mimes[$ext] = array_unique(array_merge([$media['mime']], $mimes[$ext] ?? [])); + } + } + + return MimeTypes::createFromMimes($mimes); + }; + + $container['languages'] = function ($c) { + return static::languages($c); + }; + + $container['language'] = function ($c) { + return new Language($c); + }; + } + + /** + * @param Container $container + * @return mixed + */ + public static function blueprints(Container $container) + { + /** Setup $setup */ + $setup = $container['setup']; + + /** @var UniformResourceLocator $locator */ + $locator = $container['locator']; + + $cache = $locator->findResource('cache://compiled/blueprints', true, true); + + $files = []; + $paths = $locator->findResources('blueprints://config'); + $files += (new ConfigFileFinder)->locateFiles($paths); + $paths = $locator->findResources('plugins://'); + $files += (new ConfigFileFinder)->setBase('plugins')->locateInFolders($paths, 'blueprints'); + $paths = $locator->findResources('themes://'); + $files += (new ConfigFileFinder)->setBase('themes')->locateInFolders($paths, 'blueprints'); + + $blueprints = new CompiledBlueprints($cache, $files, GRAV_ROOT); + + return $blueprints->name("master-{$setup->environment}")->load(); + } + + /** + * @param Container $container + * @return Config + */ + public static function load(Container $container) + { + /** Setup $setup */ + $setup = $container['setup']; + + /** @var UniformResourceLocator $locator */ + $locator = $container['locator']; + + $cache = $locator->findResource('cache://compiled/config', true, true); + + $files = []; + $paths = $locator->findResources('config://'); + $files += (new ConfigFileFinder)->locateFiles($paths); + $paths = $locator->findResources('plugins://'); + $files += (new ConfigFileFinder)->setBase('plugins')->locateInFolders($paths); + $paths = $locator->findResources('themes://'); + $files += (new ConfigFileFinder)->setBase('themes')->locateInFolders($paths); + + $compiled = new CompiledConfig($cache, $files, GRAV_ROOT); + $compiled->setBlueprints(function () use ($container) { + return $container['blueprints']; + }); + + $config = $compiled->name("master-{$setup->environment}")->load(); + $config->environment = $setup->environment; + + return $config; + } + + /** + * @param Container $container + * @return mixed + */ + public static function languages(Container $container) + { + /** @var Setup $setup */ + $setup = $container['setup']; + + /** @var Config $config */ + $config = $container['config']; + + /** @var UniformResourceLocator $locator */ + $locator = $container['locator']; + + $cache = $locator->findResource('cache://compiled/languages', true, true); + $files = []; + + // Process languages only if enabled in configuration. + if ($config->get('system.languages.translations', true)) { + $paths = $locator->findResources('languages://'); + $files += (new ConfigFileFinder)->locateFiles($paths); + $paths = $locator->findResources('plugins://'); + $files += (new ConfigFileFinder)->setBase('plugins')->locateInFolders($paths, 'languages'); + $paths = static::pluginFolderPaths($paths, 'languages'); + $files += (new ConfigFileFinder)->locateFiles($paths); + } + + $languages = new CompiledLanguages($cache, $files, GRAV_ROOT); + + return $languages->name("master-{$setup->environment}")->load(); + } + + /** + * Find specific paths in plugins + * + * @param array $plugins + * @param string $folder_path + * @return array + */ + protected static function pluginFolderPaths($plugins, $folder_path) + { + $paths = []; + + foreach ($plugins as $path) { + $iterator = new DirectoryIterator($path); + + /** @var DirectoryIterator $directory */ + foreach ($iterator as $directory) { + if (!$directory->isDir() || $directory->isDot()) { + continue; + } + + // Path to the languages folder + $lang_path = $directory->getPathName() . '/' . $folder_path; + + // If this folder exists, add it to the list of paths + if (file_exists($lang_path)) { + $paths []= $lang_path; + } + } + } + return $paths; + } +} diff --git a/system/src/Grav/Common/Service/ErrorServiceProvider.php b/system/src/Grav/Common/Service/ErrorServiceProvider.php new file mode 100644 index 0000000..87919a8 --- /dev/null +++ b/system/src/Grav/Common/Service/ErrorServiceProvider.php @@ -0,0 +1,30 @@ + $config->get('system.flex', [])]); + FlexFormFlash::setFlex($flex); + + $accountsEnabled = $config->get('system.accounts.type', 'regular') === 'flex'; + $pagesEnabled = $config->get('system.pages.type', 'regular') === 'flex'; + + // Add built-in types from Grav. + if ($pagesEnabled) { + $flex->addDirectoryType( + 'pages', + 'blueprints://flex/pages.yaml', + [ + 'enabled' => $pagesEnabled + ] + ); + } + if ($accountsEnabled) { + $flex->addDirectoryType( + 'user-accounts', + 'blueprints://flex/user-accounts.yaml', + [ + 'enabled' => $accountsEnabled, + 'data' => [ + 'storage' => $this->getFlexAccountsStorage($config), + ] + ] + ); + $flex->addDirectoryType( + 'user-groups', + 'blueprints://flex/user-groups.yaml', + [ + 'enabled' => $accountsEnabled + ] + ); + } + + // Call event to register Flex Directories. + $event = new FlexRegisterEvent($flex); + $container->dispatchEvent($event); + + return $flex; + }; + } + + /** + * @param Config $config + * @return array + */ + private function getFlexAccountsStorage(Config $config): array + { + $value = $config->get('system.accounts.storage', 'file'); + if (is_array($value)) { + return $value; + } + + if ($value === 'folder') { + return [ + 'class' => UserFolderStorage::class, + 'options' => [ + 'file' => 'user', + 'pattern' => '{FOLDER}/{KEY:2}/{KEY}/{FILE}{EXT}', + 'key' => 'storage_key', + 'indexed' => true, + 'case_sensitive' => false + ], + ]; + } + + if ($value === 'file') { + return [ + 'class' => UserFileStorage::class, + 'options' => [ + 'pattern' => '{FOLDER}/{KEY}{EXT}', + 'key' => 'username', + 'indexed' => true, + 'case_sensitive' => false + ], + ]; + } + + return []; + } +} diff --git a/system/src/Grav/Common/Service/InflectorServiceProvider.php b/system/src/Grav/Common/Service/InflectorServiceProvider.php new file mode 100644 index 0000000..1a27494 --- /dev/null +++ b/system/src/Grav/Common/Service/InflectorServiceProvider.php @@ -0,0 +1,32 @@ +findResource('log://grav.log', true, true); + $log->pushHandler(new StreamHandler($log_file, Logger::DEBUG)); + + return $log; + }; + } +} diff --git a/system/src/Grav/Common/Service/OutputServiceProvider.php b/system/src/Grav/Common/Service/OutputServiceProvider.php new file mode 100644 index 0000000..8157e3f --- /dev/null +++ b/system/src/Grav/Common/Service/OutputServiceProvider.php @@ -0,0 +1,39 @@ +processSite($page->templateFormat()); + }; + } +} diff --git a/system/src/Grav/Common/Service/PagesServiceProvider.php b/system/src/Grav/Common/Service/PagesServiceProvider.php new file mode 100644 index 0000000..bce0a16 --- /dev/null +++ b/system/src/Grav/Common/Service/PagesServiceProvider.php @@ -0,0 +1,140 @@ +findResource('system://pages/notfound.md'); + $page = new Page(); + $page->init(new SplFileInfo($path)); + $page->routable(false); + + return $page; + }; + + return; + } + + $container['page'] = static function (Grav $grav) { + /** @var Pages $pages */ + $pages = $grav['pages']; + + /** @var Config $config */ + $config = $grav['config']; + + /** @var Uri $uri */ + $uri = $grav['uri']; + + $path = $uri->path() ? urldecode($uri->path()) : '/'; // Don't trim to support trailing slash default routes + $page = $pages->dispatch($path); + + // Redirection tests + if ($page) { + // some debugger override logic + if ($page->debugger() === false) { + $grav['debugger']->enabled(false); + } + + if ($config->get('system.force_ssl')) { + $scheme = $uri->scheme(true); + if ($scheme !== 'https') { + $url = 'https://' . $uri->host() . $uri->uri(); + $grav->redirect($url); + } + } + + $route = $page->route(); + if ($route && \in_array($uri->method(), ['GET', 'HEAD'], true)) { + $pageExtension = $page->urlExtension(); + $url = $pages->route($route) . $pageExtension; + + if ($uri->params()) { + if ($url === '/') { //Avoid double slash + $url = $uri->params(); + } else { + $url .= $uri->params(); + } + } + if ($uri->query()) { + $url .= '?' . $uri->query(); + } + if ($uri->fragment()) { + $url .= '#' . $uri->fragment(); + } + + /** @var Language $language */ + $language = $grav['language']; + + $redirect_default_route = $page->header()->redirect_default_route ?? $config->get('system.pages.redirect_default_route', 0); + $redirectCode = (int) $redirect_default_route; + + // Language-specific redirection scenarios + if ($language->enabled() && ($language->isLanguageInUrl() xor $language->isIncludeDefaultLanguage())) { + $grav->redirect($url, $redirectCode); + } + + // Default route test and redirect + if ($redirectCode) { + $uriExtension = $uri->extension(); + $uriExtension = null !== $uriExtension ? '.' . $uriExtension : ''; + + if ($route !== $path || ($pageExtension !== $uriExtension + && \in_array($pageExtension, ['', '.htm', '.html'], true) + && \in_array($uriExtension, ['', '.htm', '.html'], true))) { + $grav->redirect($url, $redirectCode); + } + } + } + } + + // if page is not found, try some fallback stuff + if (!$page || !$page->routable()) { + // Try fallback URL stuff... + $page = $grav->fallbackUrl($path); + + if (!$page) { + $path = $grav['locator']->findResource('system://pages/notfound.md'); + $page = new Page(); + $page->init(new SplFileInfo($path)); + $page->routable(false); + } + } + + return $page; + }; + } +} diff --git a/system/src/Grav/Common/Service/RequestServiceProvider.php b/system/src/Grav/Common/Service/RequestServiceProvider.php new file mode 100644 index 0000000..715fa4c --- /dev/null +++ b/system/src/Grav/Common/Service/RequestServiceProvider.php @@ -0,0 +1,103 @@ + $headerValue) { + if ('content-type' !== strtolower($headerName)) { + continue; + } + + $contentType = strtolower(trim(explode(';', $headerValue, 2)[0])); + switch ($contentType) { + case 'application/x-www-form-urlencoded': + case 'multipart/form-data': + $post = $_POST; + break 2; + case 'application/json': + case 'application/vnd.api+json': + try { + $json = file_get_contents('php://input'); + $post = json_decode($json, true, 512, JSON_THROW_ON_ERROR); + if (!is_array($post)) { + $post = null; + } + } catch (JsonException $e) { + $post = null; + } + break 2; + } + } + } + + // Remove _url from ngnix routes. + $get = $_GET; + unset($get['_url']); + if (isset($server['QUERY_STRING'])) { + $query = $server['QUERY_STRING']; + if (strpos($query, '_url=') !== false) { + parse_str($query, $query); + unset($query['_url']); + $server['QUERY_STRING'] = http_build_query($query); + } + } + + return $creator->fromArrays($server, $headers, $_COOKIE, $get, $post, $_FILES, fopen('php://input', 'rb') ?: null); + }; + + $container['route'] = $container->factory(function () { + return clone Uri::getCurrentRoute(); + }); + } +} diff --git a/system/src/Grav/Common/Service/SchedulerServiceProvider.php b/system/src/Grav/Common/Service/SchedulerServiceProvider.php new file mode 100644 index 0000000..8b2b04b --- /dev/null +++ b/system/src/Grav/Common/Service/SchedulerServiceProvider.php @@ -0,0 +1,64 @@ +get('scheduler.modern', []); + if ($modernConfig['enabled'] ?? false) { + // Initialize components + $queuePath = $c['locator']->findResource('user-data://scheduler/queue', true, true); + $statusPath = $c['locator']->findResource('user-data://scheduler/status.yaml', true, true); + + // Set modern configuration on scheduler + $scheduler->setModernConfig($modernConfig); + + // Initialize job queue if enabled + if ($modernConfig['queue']['enabled'] ?? false) { + $jobQueue = new JobQueue($queuePath); + $scheduler->setJobQueue($jobQueue); + } + + // Initialize workers if enabled + if ($modernConfig['workers']['enabled'] ?? false) { + $workerCount = $modernConfig['workers']['count'] ?? 2; + $workers = []; + for ($i = 0; $i < $workerCount; $i++) { + $workers[] = new JobWorker("worker-{$i}"); + } + $scheduler->setWorkers($workers); + } + } + + return $scheduler; + }; + } +} diff --git a/system/src/Grav/Common/Service/SessionServiceProvider.php b/system/src/Grav/Common/Service/SessionServiceProvider.php new file mode 100644 index 0000000..c292207 --- /dev/null +++ b/system/src/Grav/Common/Service/SessionServiceProvider.php @@ -0,0 +1,134 @@ +get('system.session.enabled', false); + $cookie_secure = $config->get('system.session.secure', false) + || ($config->get('system.session.secure_https', true) && $uri->scheme(true) === 'https'); + $cookie_httponly = (bool)$config->get('system.session.httponly', true); + $cookie_lifetime = (int)$config->get('system.session.timeout', 1800); + $cookie_domain = $config->get('system.session.domain'); + $cookie_path = $config->get('system.session.path'); + $cookie_samesite = $config->get('system.session.samesite', 'Lax'); + + if (null === $cookie_domain) { + $cookie_domain = $uri->host(); + if ($cookie_domain === 'localhost') { + $cookie_domain = ''; + } + } + + if (null === $cookie_path) { + $cookie_path = '/' . trim(Uri::filterPath($uri->rootUrl(false)), '/'); + } + // Session cookie path requires trailing slash. + $cookie_path = rtrim($cookie_path, '/') . '/'; + + // Activate admin if we're inside the admin path. + $is_admin = false; + if ($config->get('plugins.admin.enabled')) { + $admin_base = '/' . trim($config->get('plugins.admin.route'), '/'); + + // Uri::route() is not processed yet, let's quickly get what we need. + $current_route = str_replace(Uri::filterPath($uri->rootUrl(false)), '', parse_url($uri->url(true), PHP_URL_PATH)); + + // Test to see if path starts with a supported language + admin base + $lang = Utils::pathPrefixedByLangCode($current_route); + $lang_admin_base = '/' . $lang . $admin_base; + + // Check no language, simple language prefix (en) and region specific language prefix (en-US). + if (Utils::startsWith($current_route, $admin_base) || Utils::startsWith($current_route, $lang_admin_base)) { + $cookie_lifetime = $config->get('plugins.admin.session.timeout', 1800); + $enabled = $is_admin = true; + } + } + + // Fix for HUGE session timeouts. + if ($cookie_lifetime > 99999999999) { + $cookie_lifetime = 9999999999; + } + + $session_prefix = $c['inflector']->hyphenize($config->get('system.session.name', 'grav-site')); + $session_uniqueness = $config->get('system.session.uniqueness', 'path') === 'path' ? substr(md5(GRAV_ROOT), 0, 7) : md5($config->get('security.salt')); + + $session_name = $session_prefix . '-' . $session_uniqueness; + + if ($is_admin && $config->get('system.session.split', true)) { + $session_name .= '-admin'; + } + + // Define session service. + $options = [ + 'name' => $session_name, + 'cookie_lifetime' => $cookie_lifetime, + 'cookie_path' => $cookie_path, + 'cookie_domain' => $cookie_domain, + 'cookie_secure' => $cookie_secure, + 'cookie_httponly' => $cookie_httponly, + 'cookie_samesite' => $cookie_samesite + ] + (array) $config->get('system.session.options'); + + $session = new Session($options); + $session->setAutoStart($enabled); + + return $session; + }; + + // Define session message service. + $container['messages'] = function ($c) { + if (!isset($c['session']) || !$c['session']->isStarted()) { + /** @var Debugger $debugger */ + $debugger = $c['debugger']; + $debugger->addMessage('Inactive session: session messages may disappear', 'warming'); + + return new Messages(); + } + + /** @var Session $session */ + $session = $c['session']; + + if (!$session->messages instanceof Messages) { + $session->messages = new Messages(); + } + + return $session->messages; + }; + } +} diff --git a/system/src/Grav/Common/Service/StreamsServiceProvider.php b/system/src/Grav/Common/Service/StreamsServiceProvider.php new file mode 100644 index 0000000..4c12ba1 --- /dev/null +++ b/system/src/Grav/Common/Service/StreamsServiceProvider.php @@ -0,0 +1,56 @@ +initializeLocator($locator); + + return $locator; + }; + + $container['streams'] = function (Container $container) { + /** @var Setup $setup */ + $setup = $container['setup']; + + /** @var UniformResourceLocator $locator */ + $locator = $container['locator']; + + // Set locator to both streams. + Stream::setLocator($locator); + ReadOnlyStream::setLocator($locator); + + return new StreamBuilder($setup->getStreams()); + }; + } +} diff --git a/system/src/Grav/Common/Service/TaskServiceProvider.php b/system/src/Grav/Common/Service/TaskServiceProvider.php new file mode 100644 index 0000000..d0f494b --- /dev/null +++ b/system/src/Grav/Common/Service/TaskServiceProvider.php @@ -0,0 +1,55 @@ +getParsedBody(); + + $task = $body['task'] ?? $c['uri']->param('task'); + if (null !== $task) { + $task = htmlspecialchars(strip_tags($task), ENT_QUOTES, 'UTF-8'); + } + + return $task ?: null; + }; + + $container['action'] = function (Grav $c) { + /** @var ServerRequestInterface $request */ + $request = $c['request']; + $body = $request->getParsedBody(); + + $action = $body['action'] ?? $c['uri']->param('action'); + if (null !== $action) { + $action = htmlspecialchars(strip_tags($action), ENT_QUOTES, 'UTF-8'); + } + + return $action ?: null; + }; + } +} diff --git a/system/src/Grav/Common/Session.php b/system/src/Grav/Common/Session.php new file mode 100644 index 0000000..7a299e8 --- /dev/null +++ b/system/src/Grav/Common/Session.php @@ -0,0 +1,202 @@ +getInstance() method instead. + */ + public static function instance() + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use ->getInstance() method instead', E_USER_DEPRECATED); + + return static::getInstance(); + } + + /** + * Initialize session. + * + * Code in this function has been moved into SessionServiceProvider class. + * + * @return void + */ + public function init() + { + if ($this->autoStart && !$this->isStarted()) { + $this->start(); + + $this->autoStart = false; + } + } + + /** + * @param bool $auto + * @return $this + */ + public function setAutoStart($auto) + { + $this->autoStart = (bool)$auto; + + return $this; + } + + /** + * Returns attributes. + * + * @return array Attributes + * @deprecated 1.5 Use ->getAll() method instead. + */ + public function all() + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use ->getAll() method instead', E_USER_DEPRECATED); + + return $this->getAll(); + } + + /** + * Checks if the session was started. + * + * @return bool + * @deprecated 1.5 Use ->isStarted() method instead. + */ + public function started() + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use ->isStarted() method instead', E_USER_DEPRECATED); + + return $this->isStarted(); + } + + /** + * Store something in session temporarily. + * + * @param string $name + * @param mixed $object + * @return $this + */ + public function setFlashObject($name, $object) + { + $this->__set($name, serialize($object)); + + return $this; + } + + /** + * Return object and remove it from session. + * + * @param string $name + * @return mixed + */ + public function getFlashObject($name) + { + $serialized = $this->__get($name); + + $object = is_string($serialized) ? unserialize($serialized, ['allowed_classes' => true]) : $serialized; + + $this->__unset($name); + + if ($name === 'files-upload') { + $grav = Grav::instance(); + + // Make sure that Forms 3.0+ has been installed. + if (null === $object && isset($grav['forms'])) { +// user_error( +// __CLASS__ . '::' . __FUNCTION__ . '(\'files-upload\') is deprecated since Grav 1.6, use $form->getFlash()->getLegacyFiles() instead', +// E_USER_DEPRECATED +// ); + + /** @var Uri $uri */ + $uri = $grav['uri']; + /** @var Forms|null $form */ + $form = $grav['forms']->getActiveForm(); // @phpstan-ignore-line (form plugin) + + $sessionField = base64_encode($uri->url); + + /** @var FormFlash|null $flash */ + $flash = $form ? $form->getFlash() : null; // @phpstan-ignore-line (form plugin) + $object = $flash && method_exists($flash, 'getLegacyFiles') ? [$sessionField => $flash->getLegacyFiles()] : null; + } + } + + return $object; + } + + /** + * Store something in cookie temporarily. + * + * @param string $name + * @param mixed $object + * @param int $time + * @return $this + * @throws JsonException + */ + public function setFlashCookieObject($name, $object, $time = 60) + { + setcookie($name, json_encode($object, JSON_THROW_ON_ERROR), $this->getCookieOptions($time)); + + return $this; + } + + /** + * Return object and remove it from the cookie. + * + * @param string $name + * @return mixed|null + * @throws JsonException + */ + public function getFlashCookieObject($name) + { + if (isset($_COOKIE[$name])) { + $cookie = $_COOKIE[$name]; + setcookie($name, '', $this->getCookieOptions(-42000)); + + return json_decode($cookie, false, 512, JSON_THROW_ON_ERROR); + } + + return null; + } + + /** + * @return void + */ + protected function onBeforeSessionStart(): void + { + $event = new BeforeSessionStartEvent($this); + + $grav = Grav::instance(); + $grav->dispatchEvent($event); + } + + /** + * @return void + */ + protected function onSessionStart(): void + { + $event = new SessionStartEvent($this); + + $grav = Grav::instance(); + $grav->dispatchEvent($event); + } +} diff --git a/system/src/Grav/Common/Taxonomy.php b/system/src/Grav/Common/Taxonomy.php new file mode 100644 index 0000000..d2dfbb3 --- /dev/null +++ b/system/src/Grav/Common/Taxonomy.php @@ -0,0 +1,181 @@ +grav = $grav; + $this->language = $grav['language']; + $this->taxonomy_map[$this->language->getLanguage()] = []; + } + + /** + * Takes an individual page and processes the taxonomies configured in its header. It + * then adds those taxonomies to the map + * + * @param PageInterface $page the page to process + * @param array|null $page_taxonomy + */ + public function addTaxonomy(PageInterface $page, $page_taxonomy = null) + { + if (!$page->published()) { + return; + } + + if (!$page_taxonomy) { + $page_taxonomy = $page->taxonomy(); + } + + if (empty($page_taxonomy)) { + return; + } + + /** @var Config $config */ + $config = $this->grav['config']; + $taxonomies = (array)$config->get('site.taxonomies'); + foreach ($taxonomies as $taxonomy) { + // Skip invalid taxonomies. + if (!\is_string($taxonomy)) { + continue; + } + $current = $page_taxonomy[$taxonomy] ?? null; + foreach ((array)$current as $item) { + $this->iterateTaxonomy($page, $taxonomy, '', $item); + } + } + } + + /** + * Iterate through taxonomy fields + * + * Reduces [taxonomy_type] to dot-notation where necessary + * + * @param PageInterface $page The Page to process + * @param string $taxonomy Taxonomy type to add + * @param string $key Taxonomy type to concatenate + * @param iterable|string $value Taxonomy value to add or iterate + * @return void + */ + public function iterateTaxonomy(PageInterface $page, string $taxonomy, string $key, $value) + { + if (is_iterable($value)) { + foreach ($value as $identifier => $item) { + $identifier = "{$key}.{$identifier}"; + $this->iterateTaxonomy($page, $taxonomy, $identifier, $item); + } + } elseif (is_string($value)) { + if (!empty($key)) { + $taxonomy .= $key; + } + $active = $this->language->getLanguage(); + $this->taxonomy_map[$active][$taxonomy][(string) $value][$page->path()] = ['slug' => $page->slug()]; + } + } + + /** + * Returns a new Page object with the sub-pages containing all the values set for a + * particular taxonomy. + * + * @param array $taxonomies taxonomies to search, eg ['tag'=>['animal','cat']] + * @param string $operator can be 'or' or 'and' (defaults to 'and') + * @return Collection Collection object set to contain matches found in the taxonomy map + */ + public function findTaxonomy($taxonomies, $operator = 'and') + { + $matches = []; + $results = []; + $active = $this->language->getLanguage(); + + foreach ((array)$taxonomies as $taxonomy => $items) { + foreach ((array)$items as $item) { + $matches[] = $this->taxonomy_map[$active][$taxonomy][$item] ?? []; + } + } + + if (strtolower($operator) === 'or') { + foreach ($matches as $match) { + $results = array_merge($results, $match); + } + } else { + $results = $matches ? array_pop($matches) : []; + foreach ($matches as $match) { + $results = array_intersect_key($results, $match); + } + } + + return new Collection($results, ['taxonomies' => $taxonomies]); + } + + /** + * Gets and Sets the taxonomy map + * + * @param array|null $var the taxonomy map + * @return array the taxonomy map + */ + public function taxonomy($var = null) + { + $active = $this->language->getLanguage(); + + if ($var) { + $this->taxonomy_map[$active] = $var; + } + + return $this->taxonomy_map[$active] ?? []; + } + + /** + * Gets item keys per taxonomy + * + * @param string $taxonomy taxonomy name + * @return array keys of this taxonomy + */ + public function getTaxonomyItemKeys($taxonomy) + { + $active = $this->language->getLanguage(); + return isset($this->taxonomy_map[$active][$taxonomy]) ? array_keys($this->taxonomy_map[$active][$taxonomy]) : []; + } +} diff --git a/system/src/Grav/Common/Theme.php b/system/src/Grav/Common/Theme.php new file mode 100644 index 0000000..c1ad0b8 --- /dev/null +++ b/system/src/Grav/Common/Theme.php @@ -0,0 +1,87 @@ +config["themes.{$this->name}"] ?? []; + } + + /** + * Persists to disk the theme parameters currently stored in the Grav Config object + * + * @param string $name The name of the theme whose config it should store. + * @return bool + */ + public static function saveConfig($name) + { + if (!$name) { + return false; + } + + $grav = Grav::instance(); + + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + + $filename = 'config://themes/' . $name . '.yaml'; + $file = YamlFile::instance((string)$locator->findResource($filename, true, true)); + $content = $grav['config']->get('themes.' . $name); + $file->save($content); + $file->free(); + unset($file); + + return true; + } + + /** + * Load blueprints. + * + * @return void + */ + protected function loadBlueprint() + { + if (!$this->blueprint) { + $grav = Grav::instance(); + /** @var Themes $themes */ + $themes = $grav['themes']; + $data = $themes->get($this->name); + \assert($data !== null); + $this->blueprint = $data->blueprints(); + } + } +} diff --git a/system/src/Grav/Common/Themes.php b/system/src/Grav/Common/Themes.php new file mode 100644 index 0000000..6db663e --- /dev/null +++ b/system/src/Grav/Common/Themes.php @@ -0,0 +1,417 @@ +grav = $grav; + $this->config = $grav['config']; + + // Register instance as autoloader for theme inheritance + spl_autoload_register([$this, 'autoloadTheme']); + } + + /** + * @return void + */ + public function init() + { + /** @var Themes $themes */ + $themes = $this->grav['themes']; + $themes->configure(); + + $this->initTheme(); + } + + /** + * @return void + */ + public function initTheme() + { + if ($this->inited === false) { + /** @var Themes $themes */ + $themes = $this->grav['themes']; + + try { + $instance = $themes->load(); + } catch (InvalidArgumentException $e) { + throw new RuntimeException($this->current() . ' theme could not be found'); + } + + // Register autoloader. + if (method_exists($instance, 'autoload')) { + $instance->autoload(); + } + + // Register event listeners. + if ($instance instanceof EventSubscriberInterface) { + /** @var EventDispatcher $events */ + $events = $this->grav['events']; + $events->addSubscriber($instance); + } + + // Register blueprints. + if (is_dir('theme://blueprints/pages')) { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + $locator->addPath('blueprints', '', ['theme://blueprints'], ['user', 'blueprints']); + } + + // Register form fields. + if (method_exists($instance, 'getFormFieldTypes')) { + /** @var Plugins $plugins */ + $plugins = $this->grav['plugins']; + $plugins->formFieldTypes = $instance->getFormFieldTypes() + $plugins->formFieldTypes; + } + + $this->grav['theme'] = $instance; + + $this->grav->fireEvent('onThemeInitialized'); + + $this->inited = true; + } + } + + /** + * Return list of all theme data with their blueprints. + * + * @return array + */ + public function all() + { + $list = []; + + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + + $iterator = $locator->getIterator('themes://'); + + /** @var DirectoryIterator $directory */ + foreach ($iterator as $directory) { + if (!$directory->isDir() || $directory->isDot()) { + continue; + } + + $theme = $directory->getFilename(); + + try { + $result = $this->get($theme); + } catch (Exception $e) { + $exception = new RuntimeException(sprintf('Theme %s: %s', $theme, $e->getMessage()), $e->getCode(), $e); + + /** @var Debugger $debugger */ + $debugger = $this->grav['debugger']; + $debugger->addMessage("Theme {$theme} cannot be loaded, please check Exceptions tab", 'error'); + $debugger->addException($exception); + + continue; + } + + if ($result) { + $list[$theme] = $result; + } + } + ksort($list, SORT_NATURAL | SORT_FLAG_CASE); + + return $list; + } + + /** + * Get theme configuration or throw exception if it cannot be found. + * + * @param string $name + * @return Data|null + * @throws RuntimeException + */ + public function get($name) + { + if (!$name) { + throw new RuntimeException('Theme name not provided.'); + } + + $blueprints = new Blueprints('themes://'); + $blueprint = $blueprints->get("{$name}/blueprints"); + + // Load default configuration. + $file = CompiledYamlFile::instance("themes://{$name}/{$name}" . YAML_EXT); + + // ensure this is a valid theme + if (!$file->exists()) { + return null; + } + + // Find thumbnail. + $thumb = "themes://{$name}/thumbnail.jpg"; + $path = $this->grav['locator']->findResource($thumb, false); + + if ($path) { + $blueprint->set('thumbnail', $this->grav['base_url'] . '/' . $path); + } + + $obj = new Data((array)$file->content(), $blueprint); + + // Override with user configuration. + $obj->merge($this->config->get('themes.' . $name) ?: []); + + // Save configuration always to user/config. + $file = CompiledYamlFile::instance("config://themes/{$name}" . YAML_EXT); + $obj->file($file); + + return $obj; + } + + /** + * Return name of the current theme. + * + * @return string + */ + public function current() + { + return (string)$this->config->get('system.pages.theme'); + } + + /** + * Load current theme. + * + * @return Theme + */ + public function load() + { + // NOTE: ALL THE LOCAL VARIABLES ARE USED INSIDE INCLUDED FILE, DO NOT REMOVE THEM! + $grav = $this->grav; + $config = $this->config; + $name = $this->current(); + $class = null; + + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + + // Start by attempting to load the theme.php file. + $file = $locator('theme://theme.php') ?: $locator("theme://{$name}.php"); + if ($file) { + // Local variables available in the file: $grav, $config, $name, $file + $class = include $file; + if (!\is_object($class) || !is_subclass_of($class, Theme::class, true)) { + $class = null; + } + } elseif (!$locator('theme://') && !defined('GRAV_CLI')) { + $response = new Response(500, [], "Theme '$name' does not exist, unable to display page."); + + $grav->close($response); + } + + // If the class hasn't been initialized yet, guess the class name and create a new instance. + if (null === $class) { + $themeClassFormat = [ + 'Grav\\Theme\\' . Inflector::camelize($name), + 'Grav\\Theme\\' . ucfirst($name) + ]; + + foreach ($themeClassFormat as $themeClass) { + if (is_subclass_of($themeClass, Theme::class, true)) { + $class = new $themeClass($grav, $config, $name); + break; + } + } + } + + // Finally if everything else fails, just create a new instance from the default Theme class. + if (null === $class) { + $class = new Theme($grav, $config, $name); + } + + $this->config->set('theme', $config->get('themes.' . $name)); + + return $class; + } + + /** + * Configure and prepare streams for current template. + * + * @return void + * @throws InvalidArgumentException + */ + public function configure() + { + $name = $this->current(); + $config = $this->config; + + $this->loadConfiguration($name, $config); + + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + + $registered = stream_get_wrappers(); + + $schemes = $config->get("themes.{$name}.streams.schemes", []); + $schemes += [ + 'theme' => [ + 'type' => 'ReadOnlyStream', + 'paths' => $locator->findResources("themes://{$name}", false) + ] + ]; + + foreach ($schemes as $scheme => $config) { + if (isset($config['paths'])) { + $locator->addPath($scheme, '', $config['paths']); + } + if (isset($config['prefixes'])) { + foreach ($config['prefixes'] as $prefix => $paths) { + $locator->addPath($scheme, $prefix, $paths); + } + } + + if (in_array($scheme, $registered, true)) { + stream_wrapper_unregister($scheme); + } + $type = !empty($config['type']) ? $config['type'] : 'ReadOnlyStream'; + if ($type[0] !== '\\') { + $type = '\\RocketTheme\\Toolbox\\StreamWrapper\\' . $type; + } + + if (!stream_wrapper_register($scheme, $type)) { + throw new InvalidArgumentException("Stream '{$type}' could not be initialized."); + } + } + + // Load languages after streams has been properly initialized + $this->loadLanguages($this->config); + } + + /** + * Load theme configuration. + * + * @param string $name Theme name + * @param Config $config Configuration class + * @return void + */ + protected function loadConfiguration($name, Config $config) + { + $themeConfig = CompiledYamlFile::instance("themes://{$name}/{$name}" . YAML_EXT)->content(); + $config->joinDefaults("themes.{$name}", $themeConfig); + } + + /** + * Load theme languages. + * Reads ALL language files from theme stream and merges them. + * + * @param Config $config Configuration class + * @return void + */ + protected function loadLanguages(Config $config) + { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + + if ($config->get('system.languages.translations', true)) { + $language_files = array_reverse($locator->findResources('theme://languages' . YAML_EXT)); + foreach ($language_files as $language_file) { + $language = CompiledYamlFile::instance($language_file)->content(); + $this->grav['languages']->mergeRecursive($language); + } + $languages_folders = array_reverse($locator->findResources('theme://languages')); + foreach ($languages_folders as $languages_folder) { + $languages = []; + $iterator = new DirectoryIterator($languages_folder); + foreach ($iterator as $file) { + if ($file->getExtension() !== 'yaml') { + continue; + } + $languages[$file->getBasename('.yaml')] = CompiledYamlFile::instance($file->getPathname())->content(); + } + $this->grav['languages']->mergeRecursive($languages); + } + } + } + + /** + * Autoload theme classes for inheritance + * + * @param string $class Class name + * @return mixed|false FALSE if unable to load $class; Class name if + * $class is successfully loaded + */ + protected function autoloadTheme($class) + { + $prefix = 'Grav\\Theme\\'; + if (false !== strpos($class, $prefix)) { + // Remove prefix from class + $class = substr($class, strlen($prefix)); + $locator = $this->grav['locator']; + + // First try lowercase version of the classname. + $path = strtolower($class); + $file = $locator("themes://{$path}/theme.php") ?: $locator("themes://{$path}/{$path}.php"); + + if ($file) { + return include_once $file; + } + + // Replace namespace tokens to directory separators + $path = $this->grav['inflector']->hyphenize($class); + $file = $locator("themes://{$path}/theme.php") ?: $locator("themes://{$path}/{$path}.php"); + + // Load class + if ($file) { + return include_once $file; + } + + // Try Old style theme classes + $path = preg_replace('#\\\|_(?!.+\\\)#', '/', $class); + \assert(null !== $path); + + $path = strtolower($path); + $file = $locator("themes://{$path}/theme.php") ?: $locator("themes://{$path}/{$path}.php"); + + // Load class + if ($file) { + return include_once $file; + } + } + + return false; + } +} diff --git a/system/src/Grav/Common/Twig/Exception/TwigException.php b/system/src/Grav/Common/Twig/Exception/TwigException.php new file mode 100644 index 0000000..ae500da --- /dev/null +++ b/system/src/Grav/Common/Twig/Exception/TwigException.php @@ -0,0 +1,21 @@ +locator = Grav::instance()['locator']; + } + + /** + * @return TwigFilter[] + */ + public function getFilters() + { + return [ + new TwigFilter('file_exists', [$this, 'file_exists']), + new TwigFilter('fileatime', [$this, 'fileatime']), + new TwigFilter('filectime', [$this, 'filectime']), + new TwigFilter('filemtime', [$this, 'filemtime']), + new TwigFilter('filesize', [$this, 'filesize']), + new TwigFilter('filetype', [$this, 'filetype']), + new TwigFilter('is_dir', [$this, 'is_dir']), + new TwigFilter('is_file', [$this, 'is_file']), + new TwigFilter('is_link', [$this, 'is_link']), + new TwigFilter('is_readable', [$this, 'is_readable']), + new TwigFilter('is_writable', [$this, 'is_writable']), + new TwigFilter('is_writeable', [$this, 'is_writable']), + new TwigFilter('lstat', [$this, 'lstat']), + new TwigFilter('getimagesize', [$this, 'getimagesize']), + new TwigFilter('exif_read_data', [$this, 'exif_read_data']), + new TwigFilter('read_exif_data', [$this, 'exif_read_data']), + new TwigFilter('exif_imagetype', [$this, 'exif_imagetype']), + new TwigFilter('hash_file', [$this, 'hash_file']), + new TwigFilter('hash_hmac_file', [$this, 'hash_hmac_file']), + new TwigFilter('md5_file', [$this, 'md5_file']), + new TwigFilter('sha1_file', [$this, 'sha1_file']), + new TwigFilter('get_meta_tags', [$this, 'get_meta_tags']), + new TwigFilter('pathinfo', [$this, 'pathinfo']), + ]; + } + + /** + * Return a list of all functions. + * + * @return TwigFunction[] + */ + public function getFunctions() + { + return [ + new TwigFunction('file_exists', [$this, 'file_exists']), + new TwigFunction('fileatime', [$this, 'fileatime']), + new TwigFunction('filectime', [$this, 'filectime']), + new TwigFunction('filemtime', [$this, 'filemtime']), + new TwigFunction('filesize', [$this, 'filesize']), + new TwigFunction('filetype', [$this, 'filetype']), + new TwigFunction('is_dir', [$this, 'is_dir']), + new TwigFunction('is_file', [$this, 'is_file']), + new TwigFunction('is_link', [$this, 'is_link']), + new TwigFunction('is_readable', [$this, 'is_readable']), + new TwigFunction('is_writable', [$this, 'is_writable']), + new TwigFunction('is_writeable', [$this, 'is_writable']), + new TwigFunction('lstat', [$this, 'lstat']), + new TwigFunction('getimagesize', [$this, 'getimagesize']), + new TwigFunction('exif_read_data', [$this, 'exif_read_data']), + new TwigFunction('read_exif_data', [$this, 'exif_read_data']), + new TwigFunction('exif_imagetype', [$this, 'exif_imagetype']), + new TwigFunction('hash_file', [$this, 'hash_file']), + new TwigFunction('hash_hmac_file', [$this, 'hash_hmac_file']), + new TwigFunction('md5_file', [$this, 'md5_file']), + new TwigFunction('sha1_file', [$this, 'sha1_file']), + new TwigFunction('get_meta_tags', [$this, 'get_meta_tags']), + new TwigFunction('pathinfo', [$this, 'pathinfo']), + ]; + } + + /** + * @param string $filename + * @return bool + */ + public function file_exists($filename): bool + { + if (!$this->checkFilename($filename)) { + return false; + } + + return file_exists($filename); + } + + /** + * @param string $filename + * @return int|false + */ + public function fileatime($filename) + { + if (!$this->checkFilename($filename)) { + return false; + } + + return fileatime($filename); + } + + /** + * @param string $filename + * @return int|false + */ + public function filectime($filename) + { + if (!$this->checkFilename($filename)) { + return false; + } + + return filectime($filename); + } + + /** + * @param string $filename + * @return int|false + */ + public function filemtime($filename) + { + if (!$this->checkFilename($filename)) { + return false; + } + + return filemtime($filename); + } + + /** + * @param string $filename + * @return int|false + */ + public function filesize($filename) + { + if (!$this->checkFilename($filename)) { + return false; + } + + return filesize($filename); + } + + /** + * @param string $filename + * @return string|false + */ + public function filetype($filename) + { + if (!$this->checkFilename($filename)) { + return false; + } + + return filetype($filename); + } + + /** + * @param string $filename + * @return bool + */ + public function is_dir($filename): bool + { + if (!$this->checkFilename($filename)) { + return false; + } + + return is_dir($filename); + } + + /** + * @param string $filename + * @return bool + */ + public function is_file($filename): bool + { + if (!$this->checkFilename($filename)) { + return false; + } + + return is_file($filename); + } + + /** + * @param string $filename + * @return bool + */ + public function is_link($filename): bool + { + if (!$this->checkFilename($filename)) { + return false; + } + + return is_link($filename); + } + + /** + * @param string $filename + * @return bool + */ + public function is_readable($filename): bool + { + if (!$this->checkFilename($filename)) { + return false; + } + + return is_readable($filename); + } + + /** + * @param string $filename + * @return bool + */ + public function is_writable($filename): bool + { + if (!$this->checkFilename($filename)) { + return false; + } + + return is_writable($filename); + } + + /** + * @param string $filename + * @return array|false + */ + public function lstat($filename) + { + if (!$this->checkFilename($filename)) { + return false; + } + + return lstat($filename); + } + + /** + * @param string $filename + * @return array|false + */ + public function getimagesize($filename) + { + if (!$this->checkFilename($filename)) { + return false; + } + + return getimagesize($filename); + } + + /** + * @param string $filename + * @param string|null $required_sections + * @param bool $as_arrays + * @param bool $read_thumbnail + * @return array|false + */ + public function exif_read_data($filename, ?string $required_sections = null, bool $as_arrays = false, bool $read_thumbnail = false) + { + if (!Utils::functionExists('exif_read_data') || !$this->checkFilename($filename)) { + return false; + } + + return @exif_read_data($filename, $required_sections, $as_arrays, $read_thumbnail); + } + + /** + * @param string $filename + * @return int|false + */ + public function exif_imagetype($filename) + { + if (!Utils::functionExists('exif_imagetype') || !$this->checkFilename($filename)) { + return false; + } + + return @exif_imagetype($filename); + } + + /** + * @param string $algo + * @param string $filename + * @param bool $binary + * @return string|false + */ + public function hash_file(string $algo, string $filename, bool $binary = false) + { + if (!$this->checkFilename($filename)) { + return false; + } + + return hash_file($algo, $filename, $binary); + } + + /** + * @param string $algo + * @param string $filename + * @param string $key + * @param bool $binary + * @return string|false + */ + public function hash_hmac_file(string $algo, string $filename, string $key, bool $binary = false) + { + if (!$this->checkFilename($filename)) { + return false; + } + + return hash_hmac_file($algo, $filename, $key, $binary); + } + + /** + * @param string $filename + * @param bool $binary + * @return string|false + */ + public function md5_file($filename, bool $binary = false) + { + if (!$this->checkFilename($filename)) { + return false; + } + + return md5_file($filename, $binary); + } + + /** + * @param string $filename + * @param bool $binary + * @return string|false + */ + public function sha1_file($filename, bool $binary = false) + { + if (!$this->checkFilename($filename)) { + return false; + } + + return sha1_file($filename, $binary); + } + + /** + * @param string $filename + * @return array|false + */ + public function get_meta_tags($filename) + { + if (!$this->checkFilename($filename)) { + return false; + } + + return get_meta_tags($filename); + } + + /** + * @param string $path + * @param int|null $flags + * @return string|string[] + */ + public function pathinfo($path, $flags = null) + { + return Utils::pathinfo($path, $flags); + } + + /** + * @param string $filename + * @return bool + */ + private function checkFilename($filename): bool + { + return is_string($filename) && (!str_contains($filename, '://') || $this->locator->isStream($filename)); + } +} diff --git a/system/src/Grav/Common/Twig/Extension/GravExtension.php b/system/src/Grav/Common/Twig/Extension/GravExtension.php new file mode 100644 index 0000000..9a8eeb2 --- /dev/null +++ b/system/src/Grav/Common/Twig/Extension/GravExtension.php @@ -0,0 +1,1756 @@ +grav = Grav::instance(); + $this->debugger = $this->grav['debugger'] ?? null; + $this->config = $this->grav['config']; + } + + /** + * Register some standard globals + * + * @return array + */ + public function getGlobals(): array + { + return [ + 'grav' => $this->grav, + ]; + } + + /** + * Return a list of all filters. + * + * @return array + */ + public function getFilters(): array + { + return [ + new TwigFilter('*ize', [$this, 'inflectorFilter']), + new TwigFilter('absolute_url', [$this, 'absoluteUrlFilter']), + new TwigFilter('contains', [$this, 'containsFilter']), + new TwigFilter('chunk_split', [$this, 'chunkSplitFilter']), + new TwigFilter('nicenumber', [$this, 'niceNumberFunc']), + new TwigFilter('nicefilesize', [$this, 'niceFilesizeFunc']), + new TwigFilter('nicetime', [$this, 'nicetimeFunc']), + new TwigFilter('defined', [$this, 'definedDefaultFilter']), + new TwigFilter('ends_with', [$this, 'endsWithFilter']), + new TwigFilter('fieldName', [$this, 'fieldNameFilter']), + new TwigFilter('parent_field', [$this, 'fieldParentFilter']), + new TwigFilter('ksort', [$this, 'ksortFilter']), + new TwigFilter('ltrim', [$this, 'ltrimFilter']), + new TwigFilter('markdown', [$this, 'markdownFunction'], ['needs_context' => true, 'is_safe' => ['html']]), + new TwigFilter('md5', [$this, 'md5Filter']), + new TwigFilter('base32_encode', [$this, 'base32EncodeFilter']), + new TwigFilter('base32_decode', [$this, 'base32DecodeFilter']), + new TwigFilter('base64_encode', [$this, 'base64EncodeFilter']), + new TwigFilter('base64_decode', [$this, 'base64DecodeFilter']), + new TwigFilter('randomize', [$this, 'randomizeFilter']), + new TwigFilter('modulus', [$this, 'modulusFilter']), + new TwigFilter('rtrim', [$this, 'rtrimFilter']), + new TwigFilter('pad', [$this, 'padFilter']), + new TwigFilter('regex_replace', [$this, 'regexReplace']), + new TwigFilter('safe_email', [$this, 'safeEmailFilter'], ['is_safe' => ['html']]), + new TwigFilter('safe_truncate', [Utils::class, 'safeTruncate']), + new TwigFilter('safe_truncate_html', [Utils::class, 'safeTruncateHTML']), + new TwigFilter('sort_by_key', [$this, 'sortByKeyFilter']), + new TwigFilter('starts_with', [$this, 'startsWithFilter']), + new TwigFilter('truncate', [Utils::class, 'truncate']), + new TwigFilter('truncate_html', [Utils::class, 'truncateHTML']), + new TwigFilter('json_decode', [$this, 'jsonDecodeFilter']), + new TwigFilter('array_unique', 'array_unique'), + new TwigFilter('basename', 'basename'), + new TwigFilter('dirname', 'dirname'), + new TwigFilter('print_r', [$this, 'print_r']), + new TwigFilter('yaml_encode', [$this, 'yamlEncodeFilter']), + new TwigFilter('yaml_decode', [$this, 'yamlDecodeFilter']), + new TwigFilter('nicecron', [$this, 'niceCronFilter']), + new TwigFilter('replace_last', [$this, 'replaceLastFilter']), + + // Translations + new TwigFilter('t', [$this, 'translate'], ['needs_environment' => true]), + new TwigFilter('tl', [$this, 'translateLanguage']), + new TwigFilter('ta', [$this, 'translateArray']), + + // Casting values + new TwigFilter('string', [$this, 'stringFilter']), + new TwigFilter('int', [$this, 'intFilter'], ['is_safe' => ['all']]), + new TwigFilter('bool', [$this, 'boolFilter']), + new TwigFilter('float', [$this, 'floatFilter'], ['is_safe' => ['all']]), + new TwigFilter('array', [$this, 'arrayFilter']), + new TwigFilter('yaml', [$this, 'yamlFilter']), + + // Object Types + new TwigFilter('get_type', [$this, 'getTypeFunc']), + new TwigFilter('of_type', [$this, 'ofTypeFunc']), + + // PHP methods + new TwigFilter('count', 'count'), + new TwigFilter('array_diff', 'array_diff'), + + // Security fixes + new TwigFilter('filter', [$this, 'filterFunc'], ['needs_environment' => true]), + new TwigFilter('map', [$this, 'mapFunc'], ['needs_environment' => true]), + new TwigFilter('reduce', [$this, 'reduceFunc'], ['needs_environment' => true]), + ]; + } + + /** + * Return a list of all functions. + * + * @return array + */ + public function getFunctions(): array + { + return [ + new TwigFunction('array', [$this, 'arrayFilter']), + new TwigFunction('array_key_value', [$this, 'arrayKeyValueFunc']), + new TwigFunction('array_key_exists', 'array_key_exists'), + new TwigFunction('array_unique', 'array_unique'), + new TwigFunction('array_intersect', [$this, 'arrayIntersectFunc']), + new TwigFunction('array_diff', 'array_diff'), + new TwigFunction('authorize', [$this, 'authorize']), + new TwigFunction('debug', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]), + new TwigFunction('dump', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]), + new TwigFunction('vardump', [$this, 'vardumpFunc']), + new TwigFunction('print_r', [$this, 'print_r']), + new TwigFunction('http_response_code', 'http_response_code'), + new TwigFunction('evaluate', [$this, 'evaluateStringFunc'], ['needs_context' => true]), + new TwigFunction('evaluate_twig', [$this, 'evaluateTwigFunc'], ['needs_context' => true]), + new TwigFunction('gist', [$this, 'gistFunc']), + new TwigFunction('nonce_field', [$this, 'nonceFieldFunc']), + new TwigFunction('pathinfo', 'pathinfo'), + new TwigFunction('parseurl', 'parse_url'), + new TwigFunction('random_string', [$this, 'randomStringFunc']), + new TwigFunction('repeat', [$this, 'repeatFunc']), + new TwigFunction('regex_replace', [$this, 'regexReplace']), + new TwigFunction('regex_filter', [$this, 'regexFilter']), + new TwigFunction('regex_match', [$this, 'regexMatch']), + new TwigFunction('regex_split', [$this, 'regexSplit']), + new TwigFunction('string', [$this, 'stringFilter']), + new TwigFunction('url', [$this, 'urlFunc']), + new TwigFunction('json_decode', [$this, 'jsonDecodeFilter']), + new TwigFunction('get_cookie', [$this, 'getCookie']), + new TwigFunction('redirect_me', [$this, 'redirectFunc']), + new TwigFunction('range', [$this, 'rangeFunc']), + new TwigFunction('isajaxrequest', [$this, 'isAjaxFunc']), + new TwigFunction('exif', [$this, 'exifFunc']), + new TwigFunction('media_directory', [$this, 'mediaDirFunc']), + new TwigFunction('body_class', [$this, 'bodyClassFunc'], ['needs_context' => true]), + new TwigFunction('theme_var', [$this, 'themeVarFunc'], ['needs_context' => true]), + new TwigFunction('header_var', [$this, 'pageHeaderVarFunc'], ['needs_context' => true]), + new TwigFunction('read_file', [$this, 'readFileFunc']), + new TwigFunction('nicenumber', [$this, 'niceNumberFunc']), + new TwigFunction('nicefilesize', [$this, 'niceFilesizeFunc']), + new TwigFunction('nicetime', [$this, 'nicetimeFunc']), + new TwigFunction('cron', [$this, 'cronFunc']), + new TwigFunction('svg_image', [$this, 'svgImageFunction']), + new TwigFunction('xss', [$this, 'xssFunc']), + new TwigFunction('unique_id', [$this, 'uniqueId']), + + // Translations + new TwigFunction('t', [$this, 'translate'], ['needs_environment' => true]), + new TwigFunction('tl', [$this, 'translateLanguage']), + new TwigFunction('ta', [$this, 'translateArray']), + + // Object Types + new TwigFunction('get_type', [$this, 'getTypeFunc']), + new TwigFunction('of_type', [$this, 'ofTypeFunc']), + + // PHP methods + new TwigFunction('is_numeric', 'is_numeric'), + new TwigFunction('is_iterable', 'is_iterable'), + new TwigFunction('is_countable', 'is_countable'), + new TwigFunction('is_null', 'is_null'), + new TwigFunction('is_string', 'is_string'), + new TwigFunction('is_array', 'is_array'), + new TwigFunction('is_object', 'is_object'), + new TwigFunction('count', 'count'), + new TwigFunction('array_diff', 'array_diff'), + new TwigFunction('parse_url', 'parse_url'), + + // Security fixes + new TwigFunction('filter', [$this, 'filterFunc'], ['needs_environment' => true]), + new TwigFunction('map', [$this, 'mapFunc'], ['needs_environment' => true]), + new TwigFunction('reduce', [$this, 'reduceFunc'], ['needs_environment' => true]), + ]; + } + + /** + * @return array + */ + public function getTokenParsers(): array + { + return [ + new TwigTokenParserRender(), + new TwigTokenParserThrow(), + new TwigTokenParserTryCatch(), + new TwigTokenParserScript(), + new TwigTokenParserStyle(), + new TwigTokenParserLink(), + new TwigTokenParserMarkdown(), + new TwigTokenParserSwitch(), + new TwigTokenParserCache(), + ]; + } + + /** + * @param mixed $var + * @return string + */ + public function print_r($var) + { + return print_r($var, true); + } + + /** + * Filters field name by changing dot notation into array notation. + * + * @param string $str + * @return string + */ + public function fieldNameFilter($str) + { + $path = explode('.', rtrim($str, '.')); + + return array_shift($path) . ($path ? '[' . implode('][', $path) . ']' : ''); + } + + /** + * Filters field name by changing dot notation into array notation. + * + * @param string $str + * @return string + */ + public function fieldParentFilter($str) + { + $path = explode('.', rtrim($str, '.')); + array_pop($path); + + return implode('.', $path); + } + + /** + * Protects email address. + * + * @param string $str + * @return string + */ + public function safeEmailFilter($str) + { + static $list = [ + '"' => '"', + "'" => ''', + '&' => '&', + '<' => '<', + '>' => '>', + '@' => '@' + ]; + + $characters = mb_str_split($str, 1, 'UTF-8'); + + $encoded = ''; + foreach ($characters as $chr) { + $encoded .= $list[$chr] ?? (random_int(0, 1) ? '&#' . mb_ord($chr) . ';' : $chr); + } + + return $encoded; + } + + /** + * Returns array in a random order. + * + * @param array|Traversable $original + * @param int $offset Can be used to return only slice of the array. + * @return array + */ + public function randomizeFilter($original, $offset = 0) + { + if ($original instanceof Traversable) { + $original = iterator_to_array($original, false); + } + + if (!is_array($original)) { + return $original; + } + + $sorted = []; + $random = array_slice($original, $offset); + shuffle($random); + + $sizeOf = count($original); + for ($x = 0; $x < $sizeOf; $x++) { + if ($x < $offset) { + $sorted[] = $original[$x]; + } else { + $sorted[] = array_shift($random); + } + } + + return $sorted; + } + + /** + * Returns the modulus of an integer + * + * @param string|int $number + * @param int $divider + * @param array|null $items array of items to select from to return + * @return int + */ + public function modulusFilter($number, $divider, $items = null) + { + if (is_string($number)) { + $number = strlen($number); + } + + $remainder = $number % $divider; + + if (is_array($items)) { + return $items[$remainder] ?? $items[0]; + } + + return $remainder; + } + + /** + * Inflector supports following notations: + * + * `{{ 'person'|pluralize }} => people` + * `{{ 'shoes'|singularize }} => shoe` + * `{{ 'welcome page'|titleize }} => "Welcome Page"` + * `{{ 'send_email'|camelize }} => SendEmail` + * `{{ 'CamelCased'|underscorize }} => camel_cased` + * `{{ 'Something Text'|hyphenize }} => something-text` + * `{{ 'something_text_to_read'|humanize }} => "Something text to read"` + * `{{ '181'|monthize }} => 5` + * `{{ '10'|ordinalize }} => 10th` + * + * @param string $action + * @param string $data + * @param int|null $count + * @return string + */ + public function inflectorFilter($action, $data, $count = null) + { + $action .= 'ize'; + + /** @var Inflector $inflector */ + $inflector = $this->grav['inflector']; + + if (in_array( + $action, + ['titleize', 'camelize', 'underscorize', 'hyphenize', 'humanize', 'ordinalize', 'monthize'], + true + )) { + return $inflector->{$action}($data); + } + + if (in_array($action, ['pluralize', 'singularize'], true)) { + return $count ? $inflector->{$action}($data, $count) : $inflector->{$action}($data); + } + + return $data; + } + + /** + * Return MD5 hash from the input. + * + * @param string $str + * @return string + */ + public function md5Filter($str) + { + return md5($str); + } + + /** + * Return Base32 encoded string + * + * @param string $str + * @return string + */ + public function base32EncodeFilter($str) + { + return Base32::encode($str); + } + + /** + * Return Base32 decoded string + * + * @param string $str + * @return string + */ + public function base32DecodeFilter($str) + { + return Base32::decode($str); + } + + /** + * Return Base64 encoded string + * + * @param string $str + * @return string + */ + public function base64EncodeFilter($str) + { + return base64_encode((string) $str); + } + + /** + * Return Base64 decoded string + * + * @param string $str + * @return string|false + */ + public function base64DecodeFilter($str) + { + return base64_decode($str); + } + + /** + * Sorts a collection by key + * + * @param array $input + * @param string $filter + * @param int $direction + * @param int $sort_flags + * @return array + */ + public function sortByKeyFilter($input, $filter, $direction = SORT_ASC, $sort_flags = SORT_REGULAR) + { + return Utils::sortArrayByKey($input, $filter, $direction, $sort_flags); + } + + /** + * Return ksorted collection. + * + * @param array|null $array + * @return array + */ + public function ksortFilter($array) + { + if (null === $array) { + $array = []; + } + ksort($array); + + return $array; + } + + /** + * Wrapper for chunk_split() function + * + * @param string $value + * @param int $chars + * @param string $split + * @return string + */ + public function chunkSplitFilter($value, $chars, $split = '-') + { + return chunk_split($value, $chars, $split); + } + + /** + * determine if a string contains another + * + * @param string $haystack + * @param string $needle + * @return string|bool + * @todo returning $haystack here doesn't make much sense + */ + public function containsFilter($haystack, $needle) + { + if (empty($needle)) { + return $haystack; + } + + return (strpos($haystack, (string) $needle) !== false); + } + + /** + * Gets a human readable output for cron syntax + * + * @param string $at + * @return string + */ + public function niceCronFilter($at) + { + $cron = new Cron($at); + return $cron->getText('en'); + } + + /** + * @param string|mixed $str + * @param string $search + * @param string $replace + * @return string|mixed + */ + public function replaceLastFilter($str, $search, $replace) + { + if (is_string($str) && ($pos = mb_strrpos($str, $search)) !== false) { + $str = mb_substr($str, 0, $pos) . $replace . mb_substr($str, $pos + mb_strlen($search)); + } + + return $str; + } + + /** + * Get Cron object for a crontab 'at' format + * + * @param string $at + * @return CronExpression + */ + public function cronFunc($at) + { + return CronExpression::factory($at); + } + + /** + * displays a facebook style 'time ago' formatted date/time + * + * @param string $date + * @param bool $long_strings + * @param bool $show_tense + * @return string + */ + public function nicetimeFunc($date, $long_strings = true, $show_tense = true) + { + if (empty($date)) { + return $this->grav['language']->translate('GRAV.NICETIME.NO_DATE_PROVIDED'); + } + + if ($long_strings) { + $periods = [ + 'NICETIME.SECOND', + 'NICETIME.MINUTE', + 'NICETIME.HOUR', + 'NICETIME.DAY', + 'NICETIME.WEEK', + 'NICETIME.MONTH', + 'NICETIME.YEAR', + 'NICETIME.DECADE' + ]; + } else { + $periods = [ + 'NICETIME.SEC', + 'NICETIME.MIN', + 'NICETIME.HR', + 'NICETIME.DAY', + 'NICETIME.WK', + 'NICETIME.MO', + 'NICETIME.YR', + 'NICETIME.DEC' + ]; + } + + $lengths = ['60', '60', '24', '7', '4.35', '12', '10']; + + $now = time(); + + // check if unix timestamp + if ((string)(int)$date === (string)$date) { + $unix_date = $date; + } else { + $unix_date = strtotime($date); + } + + // check validity of date + if (empty($unix_date)) { + return $this->grav['language']->translate('GRAV.NICETIME.BAD_DATE'); + } + + // is it future date or past date + if ($now > $unix_date) { + $difference = $now - $unix_date; + $tense = $this->grav['language']->translate('GRAV.NICETIME.AGO'); + } elseif ($now == $unix_date) { + $difference = $now - $unix_date; + $tense = $this->grav['language']->translate('GRAV.NICETIME.JUST_NOW'); + } else { + $difference = $unix_date - $now; + $tense = $this->grav['language']->translate('GRAV.NICETIME.FROM_NOW'); + } + + for ($j = 0; $difference >= $lengths[$j] && $j < count($lengths) - 1; $j++) { + $difference /= $lengths[$j]; + } + + $difference = round($difference); + + if ($difference != 1) { + $periods[$j] .= '_PLURAL'; + } + + if ($this->grav['language']->getTranslation( + $this->grav['language']->getLanguage(), + $periods[$j] . '_MORE_THAN_TWO' + ) + ) { + if ($difference > 2) { + $periods[$j] .= '_MORE_THAN_TWO'; + } + } + + $periods[$j] = $this->grav['language']->translate('GRAV.'.$periods[$j]); + + if ($now == $unix_date) { + return $tense; + } + + $time = "{$difference} {$periods[$j]}"; + $time .= $show_tense ? " {$tense}" : ''; + + return $time; + } + + /** + * Allow quick check of a string for XSS Vulnerabilities + * + * @param string|array $data + * @return bool|string|array + */ + public function xssFunc($data) + { + if (!is_array($data)) { + return Security::detectXss($data); + } + + $results = Security::detectXssFromArray($data); + $results_parts = array_map(static function ($value, $key) { + return $key.': \''.$value . '\''; + }, array_values($results), array_keys($results)); + + return implode(', ', $results_parts); + } + + /** + * Generates a random string with configurable length, prefix and suffix. + * Unlike the built-in `uniqid()`, this string is non-conflicting and safe + * + * @param int $length + * @param array $options + * @return string + * @throws \Exception + */ + public function uniqueId(int $length = 9, array $options = ['prefix' => '', 'suffix' => '']): string + { + return Utils::uniqueId($length, $options); + } + + /** + * @param string $string + * @return string + */ + public function absoluteUrlFilter($string) + { + $url = $this->grav['uri']->base(); + $string = preg_replace('/((?:href|src) *= *[\'"](?!(http|ftp)))/i', "$1$url", $string); + + return $string; + } + + /** + * @param array $context + * @param string $string + * @param bool $block Block or Line processing + * @return string + */ + public function markdownFunction($context, $string, $block = true) + { + $page = $context['page'] ?? null; + return Utils::processMarkdown($string, $block, $page); + } + + /** + * @param string $haystack + * @param string $needle + * @return bool + */ + public function startsWithFilter($haystack, $needle) + { + return Utils::startsWith($haystack, $needle); + } + + /** + * @param string $haystack + * @param string $needle + * @return bool + */ + public function endsWithFilter($haystack, $needle) + { + return Utils::endsWith($haystack, $needle); + } + + /** + * @param mixed $value + * @param null $default + * @return mixed|null + */ + public function definedDefaultFilter($value, $default = null) + { + return $value ?? $default; + } + + /** + * @param string $value + * @param string|null $chars + * @return string + */ + public function rtrimFilter($value, $chars = null) + { + return null !== $chars ? rtrim($value, $chars) : rtrim($value); + } + + /** + * @param string $value + * @param string|null $chars + * @return string + */ + public function ltrimFilter($value, $chars = null) + { + return null !== $chars ? ltrim($value, $chars) : ltrim($value); + } + + /** + * Returns a string from a value. If the value is array, return it json encoded + * + * @param mixed $value + * @return string + */ + public function stringFilter($value) + { + // Format the array as a string + if (is_array($value)) { + return json_encode($value); + } + + // Boolean becomes '1' or '0' + if (is_bool($value)) { + $value = (int)$value; + } + + // Cast the other values to string. + return (string)$value; + } + + /** + * Casts input to int. + * + * @param mixed $input + * @return int + */ + public function intFilter($input) + { + return (int) $input; + } + + /** + * Casts input to bool. + * + * @param mixed $input + * @return bool + */ + public function boolFilter($input) + { + return (bool) $input; + } + + /** + * Casts input to float. + * + * @param mixed $input + * @return float + */ + public function floatFilter($input) + { + return (float) $input; + } + + /** + * Casts input to array. + * + * @param mixed $input + * @return array + */ + public function arrayFilter($input) + { + if (is_array($input)) { + return $input; + } + + if (is_object($input)) { + if (method_exists($input, 'toArray')) { + return $input->toArray(); + } + + if ($input instanceof Iterator) { + return iterator_to_array($input); + } + } + + return (array)$input; + } + + /** + * @param array|object $value + * @param int|null $inline + * @param int|null $indent + * @return string + */ + public function yamlFilter($value, $inline = null, $indent = null): string + { + return Yaml::dump($value, $inline, $indent); + } + + /** + * @param Environment $twig + * @return string + */ + public function translate(Environment $twig, ...$args) + { + // If admin and tu filter provided, use it + if (isset($this->grav['admin'])) { + $numargs = count($args); + $lang = null; + + if (($numargs === 3 && is_array($args[1])) || ($numargs === 2 && !is_array($args[1]))) { + $lang = array_pop($args); + /** @var Language $language */ + $language = $this->grav['language']; + if (is_string($lang) && !$language->getLanguageCode($lang)) { + $args[] = $lang; + $lang = null; + } + } elseif ($numargs === 2 && is_array($args[1])) { + $subs = array_pop($args); + $args = array_merge($args, $subs); + } + + return $this->grav['admin']->translate($args, $lang); + } + + $translation = $this->grav['language']->translate($args); + + if ($this->config->get('system.languages.debug', false)) { + $debugger = $this->grav['debugger']; + $debugger->addMessage("$args[0] -> $translation", 'debug'); + } + + return $translation; + } + + /** + * Translate Strings + * + * @param string|array $args + * @param array|null $languages + * @param bool $array_support + * @param bool $html_out + * @return string + */ + public function translateLanguage($args, array $languages = null, $array_support = false, $html_out = false) + { + /** @var Language $language */ + $language = $this->grav['language']; + + return $language->translate($args, $languages, $array_support, $html_out); + } + + /** + * @param string $key + * @param string $index + * @param array|null $lang + * @return string + */ + public function translateArray($key, $index, $lang = null) + { + /** @var Language $language */ + $language = $this->grav['language']; + + return $language->translateArray($key, $index, $lang); + } + + /** + * Repeat given string x times. + * + * @param string $input + * @param int $multiplier + * + * @return string + */ + public function repeatFunc($input, $multiplier) + { + return str_repeat($input, (int) $multiplier); + } + + /** + * Return URL to the resource. + * + * @example {{ url('theme://images/logo.png')|default('http://www.placehold.it/150x100/f4f4f4') }} + * + * @param string $input Resource to be located. + * @param bool $domain True to include domain name. + * @param bool $failGracefully If true, return URL even if the file does not exist. + * @return string|false Returns url to the resource or null if resource was not found. + */ + public function urlFunc($input, $domain = false, $failGracefully = false) + { + return Utils::url($input, $domain, $failGracefully); + } + + /** + * This function will evaluate Twig $twig through the $environment, and return its results. + * + * @param array $context + * @param string $twig + * @return mixed + */ + public function evaluateTwigFunc($context, $twig) + { + + $loader = new FilesystemLoader('.'); + $env = new Environment($loader); + $env->addExtension($this); + + $template = $env->createTemplate($twig); + + return $template->render($context); + } + + /** + * This function will evaluate a $string through the $environment, and return its results. + * + * @param array $context + * @param string $string + * @return mixed + */ + public function evaluateStringFunc($context, $string) + { + return $this->evaluateTwigFunc($context, "{{ $string }}"); + } + + /** + * Based on Twig\Extension\Debug / twig_var_dump + * (c) 2011 Fabien Potencier + * + * @param Environment $env + * @param array $context + */ + public function dump(Environment $env, $context) + { + if (!$env->isDebug() || !$this->debugger) { + return; + } + + $count = func_num_args(); + if (2 === $count) { + $data = []; + foreach ($context as $key => $value) { + if (is_object($value)) { + if (method_exists($value, 'toArray')) { + $data[$key] = $value->toArray(); + } else { + $data[$key] = 'Object (' . get_class($value) . ')'; + } + } else { + $data[$key] = $value; + } + } + $this->debugger->addMessage($data, 'debug'); + } else { + for ($i = 2; $i < $count; $i++) { + $var = func_get_arg($i); + $this->debugger->addMessage($var, 'debug'); + } + } + } + + /** + * Output a Gist + * + * @param string $id + * @param string|false $file + * @return string + */ + public function gistFunc($id, $file = false) + { + $url = 'https://gist.github.com/' . $id . '.js'; + if ($file) { + $url .= '?file=' . $file; + } + return ''; + } + + /** + * Generate a random string + * + * @param int $count + * @return string + */ + public function randomStringFunc($count = 5) + { + return Utils::generateRandomString($count); + } + + /** + * Pad a string to a certain length with another string + * + * @param string $input + * @param int $pad_length + * @param string $pad_string + * @param int $pad_type + * @return string + */ + public static function padFilter($input, $pad_length, $pad_string = ' ', $pad_type = STR_PAD_RIGHT) + { + return str_pad($input, (int)$pad_length, $pad_string, $pad_type); + } + + /** + * Workaround for twig associative array initialization + * Returns a key => val array + * + * @param string $key key of item + * @param string $val value of item + * @param array|null $current_array optional array to add to + * @return array + */ + public function arrayKeyValueFunc($key, $val, $current_array = null) + { + if (empty($current_array)) { + return array($key => $val); + } + + $current_array[$key] = $val; + + return $current_array; + } + + /** + * Wrapper for array_intersect() method + * + * @param array|Collection $array1 + * @param array|Collection $array2 + * @return array|Collection + */ + public function arrayIntersectFunc($array1, $array2) + { + if ($array1 instanceof Collection && $array2 instanceof Collection) { + return $array1->intersect($array2)->toArray(); + } + + return array_intersect($array1, $array2); + } + + /** + * Translate a string + * + * @return string + */ + public function translateFunc() + { + return $this->grav['language']->translate(func_get_args()); + } + + /** + * Authorize an action. Returns true if the user is logged in and + * has the right to execute $action. + * + * @param string|array $action An action or a list of actions. Each + * entry can be a string like 'group.action' + * or without dot notation an associative + * array. + * @return bool Returns TRUE if the user is authorized to + * perform the action, FALSE otherwise. + */ + public function authorize($action) + { + // Admin can use Flex users even if the site does not; make sure we use the right version of the user. + $admin = $this->grav['admin'] ?? null; + if ($admin) { + $user = $admin->user; + } else { + /** @var UserInterface|null $user */ + $user = $this->grav['user'] ?? null; + } + + if (!$user) { + return false; + } + + if (is_array($action)) { + if (Utils::isAssoc($action)) { + // Handle nested access structure. + $actions = Utils::arrayFlattenDotNotation($action); + } else { + // Handle simple access list. + $actions = array_combine($action, array_fill(0, count($action), true)); + } + } else { + // Handle single action. + $actions = [(string)$action => true]; + } + + $count = count($actions); + foreach ($actions as $act => $authenticated) { + // Ignore 'admin.super' if it's not the only value to be checked. + if ($act === 'admin.super' && $count > 1 && $user instanceof FlexObjectInterface) { + continue; + } + + $auth = $user->authorize($act) ?? false; + if (is_bool($auth) && $auth === Utils::isPositive($authenticated)) { + return true; + } + } + + return false; + } + + /** + * Used to add a nonce to a form. Call {{ nonce_field('action') }} specifying a string representing the action. + * + * For maximum protection, ensure that the string representing the action is as specific as possible + * + * @param string $action the action + * @param string $nonceParamName a custom nonce param name + * @return string the nonce input field + */ + public function nonceFieldFunc($action, $nonceParamName = 'nonce') + { + $string = ''; + + return $string; + } + + /** + * Decodes string from JSON. + * + * @param string $str + * @param bool $assoc + * @param int $depth + * @param int $options + * @return array + */ + public function jsonDecodeFilter($str, $assoc = false, $depth = 512, $options = 0) + { + if ($str === null) { + $str = ''; + } + return json_decode(html_entity_decode($str, ENT_COMPAT | ENT_HTML401, 'UTF-8'), $assoc, $depth, $options); + } + + /** + * Used to retrieve a cookie value + * + * @param string $key The cookie name to retrieve + * @return string + */ + public function getCookie($key) + { + $cookie_value = filter_input(INPUT_COOKIE, $key); + + if ($cookie_value === null) { + return null; + } + + return htmlspecialchars(strip_tags($cookie_value), ENT_QUOTES, 'UTF-8'); + } + + /** + * Twig wrapper for PHP's preg_replace method + * + * @param string|string[] $subject the content to perform the replacement on + * @param string|string[] $pattern the regex pattern to use for matches + * @param string|string[] $replace the replacement value either as a string or an array of replacements + * @param int $limit the maximum possible replacements for each pattern in each subject + * @return string|string[]|null the resulting content + */ + public function regexReplace($subject, $pattern, $replace, $limit = -1) + { + return preg_replace($pattern, $replace, $subject, $limit); + } + + /** + * Twig wrapper for PHP's preg_grep method + * + * @param array $array + * @param string $regex + * @param int $flags + * @return array + */ + public function regexFilter($array, $regex, $flags = 0) + { + return preg_grep($regex, $array, $flags); + } + + /** + * Twig wrapper for PHP's preg_match method + * + * @param string $subject the content to perform the match on + * @param string $pattern the regex pattern to use for match + * @param int $flags + * @param int $offset + * @return array|false returns the matches if there is at least one match in the subject for a given pattern or null if not. + */ + public function regexMatch($subject, $pattern, $flags = 0, $offset = 0) + { + if (preg_match($pattern, $subject, $matches, $flags, $offset) === false) { + return false; + } + + return $matches; + } + + /** + * Twig wrapper for PHP's preg_split method + * + * @param string $subject the content to perform the split on + * @param string $pattern the regex pattern to use for split + * @param int $limit the maximum possible splits for the given pattern + * @param int $flags + * @return array|false the resulting array after performing the split operation + */ + public function regexSplit($subject, $pattern, $limit = -1, $flags = 0) + { + return preg_split($pattern, $subject, $limit, $flags); + } + + /** + * redirect browser from twig + * + * @param string $url the url to redirect to + * @param int $statusCode statusCode, default 303 + * @return void + */ + public function redirectFunc($url, $statusCode = 303) + { + $response = new Response($statusCode, ['location' => $url]); + + $this->grav->close($response); + } + + /** + * Generates an array containing a range of elements, optionally stepped + * + * @param int $start Minimum number, default 0 + * @param int $end Maximum number, default `getrandmax()` + * @param int $step Increment between elements in the sequence, default 1 + * @return array + */ + public function rangeFunc($start = 0, $end = 100, $step = 1) + { + return range($start, $end, $step); + } + + /** + * Check if HTTP_X_REQUESTED_WITH has been set to xmlhttprequest, + * in which case we may unsafely assume ajax. Non critical use only. + * + * @return bool True if HTTP_X_REQUESTED_WITH exists and has been set to xmlhttprequest + */ + public function isAjaxFunc() + { + return ( + !empty($_SERVER['HTTP_X_REQUESTED_WITH']) + && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'); + } + + /** + * Get the Exif data for a file + * + * @param string $image + * @param bool $raw + * @return mixed + */ + public function exifFunc($image, $raw = false) + { + if (isset($this->grav['exif'])) { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + + if ($locator->isStream($image)) { + $image = $locator->findResource($image); + } + + $exif_reader = $this->grav['exif']->getReader(); + + if ($image && file_exists($image) && $this->config->get('system.media.auto_metadata_exif') && $exif_reader) { + $exif_data = $exif_reader->read($image); + + if ($exif_data) { + if ($raw) { + return $exif_data->getRawData(); + } + + return $exif_data->getData(); + } + } + } + + return null; + } + + /** + * Simple function to read a file based on a filepath and output it + * + * @param string $filepath + * @return bool|string + */ + public function readFileFunc($filepath) + { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + + if ($locator->isStream($filepath)) { + $filepath = $locator->findResource($filepath); + } + + if ($filepath && file_exists($filepath)) { + return file_get_contents($filepath); + } + + return false; + } + + /** + * Process a folder as Media and return a media object + * + * @param string $media_dir + * @return Media|null + */ + public function mediaDirFunc($media_dir) + { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + + if ($locator->isStream($media_dir)) { + $media_dir = $locator->findResource($media_dir); + } + + if ($media_dir && file_exists($media_dir)) { + return new Media($media_dir); + } + + return null; + } + + /** + * Dump a variable to the browser + * + * @param mixed $var + * @return void + */ + public function vardumpFunc($var) + { + dump($var); + } + + /** + * Returns a nicer more readable filesize based on bytes + * + * @param int $bytes + * @return string + */ + public function niceFilesizeFunc($bytes) + { + return Utils::prettySize($bytes); + } + + /** + * Returns a nicer more readable number + * + * @param int|float|string $n + * @return string|bool + */ + public function niceNumberFunc($n) + { + if (!is_float($n) && !is_int($n)) { + if (!is_string($n) || $n === '') { + return false; + } + + // Strip any thousand formatting and find the first number. + $list = array_filter(preg_split("/\D+/", str_replace(',', '', $n))); + $n = reset($list); + + if (!is_numeric($n)) { + return false; + } + + $n = (float)$n; + } + + // now filter it; + if ($n > 1000000000000) { + return round($n/1000000000000, 2).' t'; + } + if ($n > 1000000000) { + return round($n/1000000000, 2).' b'; + } + if ($n > 1000000) { + return round($n/1000000, 2).' m'; + } + if ($n > 1000) { + return round($n/1000, 2).' k'; + } + + return number_format($n); + } + + /** + * Get a theme variable + * Will try to get the variable for the current page, if not found, it tries it's parent page on up to root. + * If still not found, will use the theme's configuration value, + * If still not found, will use the $default value passed in + * + * @param array $context Twig Context + * @param string $var variable to be found (using dot notation) + * @param null $default the default value to be used as last resort + * @param PageInterface|null $page an optional page to use for the current page + * @param bool $exists toggle to simply return the page where the variable is set, else null + * @return mixed + */ + public function themeVarFunc($context, $var, $default = null, $page = null, $exists = false) + { + $page = $page ?? $context['page'] ?? Grav::instance()['page'] ?? null; + + // Try to find var in the page headers + if ($page instanceof PageInterface && $page->exists()) { + // Loop over pages and look for header vars + while ($page && !$page->root()) { + $header = new Data((array)$page->header()); + $value = $header->get($var); + if (isset($value)) { + if ($exists) { + return $page; + } + + return $value; + } + $page = $page->parent(); + } + } + + if ($exists) { + return false; + } + + return Grav::instance()['config']->get('theme.' . $var, $default); + } + + /** + * Look for a page header variable in an array of pages working its way through until a value is found + * + * @param array $context + * @param string $var the variable to look for in the page header + * @param string|string[]|null $pages array of pages to check (current page upwards if not null) + * @return mixed + * @deprecated 1.7 Use themeVarFunc() instead + */ + public function pageHeaderVarFunc($context, $var, $pages = null) + { + if (is_array($pages)) { + $page = array_shift($pages); + } else { + $page = null; + } + return $this->themeVarFunc($context, $var, null, $page); + } + + /** + * takes an array of classes, and if they are not set on body_classes + * look to see if they are set in theme config + * + * @param array $context + * @param string|string[] $classes + * @return string + */ + public function bodyClassFunc($context, $classes) + { + + $header = $context['page']->header(); + $body_classes = $header->body_classes ?? ''; + + foreach ((array)$classes as $class) { + if (!empty($body_classes) && Utils::contains($body_classes, $class)) { + continue; + } + + $val = $this->config->get('theme.' . $class, false) ? $class : false; + $body_classes .= $val ? ' ' . $val : ''; + } + + return $body_classes; + } + + /** + * Returns the content of an SVG image and adds extra classes as needed + * + * @param string $path + * @param string|null $classes + * @return string|string[]|null + */ + public static function svgImageFunction($path, $classes = null, $strip_style = false) + { + $path = Utils::fullPath($path); + + $classes = $classes ?: ''; + + if (file_exists($path) && !is_dir($path)) { + $svg = file_get_contents($path); + $classes = " inline-block $classes"; + $matched = false; + + //Remove xml tag if it exists + $svg = preg_replace('/^<\?xml.*\?>/','', $svg); + + //Strip style if needed + if ($strip_style) { + $svg = preg_replace('//s', '', $svg); + } + + //Look for existing class + $svg = preg_replace_callback('/^]*(class=\"([^"]*)\")[^>]*>/', function($matches) use ($classes, &$matched) { + if (isset($matches[2])) { + $new_classes = $matches[2] . $classes; + $matched = true; + return str_replace($matches[1], "class=\"$new_classes\"", $matches[0]); + } + return $matches[0]; + }, $svg + ); + + // no matches found just add the class + if (!$matched) { + $classes = trim($classes); + $svg = str_replace('jsonSerialize(); + } elseif (method_exists($data, 'toArray')) { + $data = $data->toArray(); + } else { + $data = json_decode(json_encode($data), true); + } + } + + return Yaml::dump($data, $inline); + } + + /** + * Decode/Parse data from YAML format + * + * @param string $data + * @return array + */ + public function yamlDecodeFilter($data) + { + return Yaml::parse($data); + } + + /** + * Function/Filter to return the type of variable + * + * @param mixed $var + * @return string + */ + public function getTypeFunc($var) + { + return gettype($var); + } + + /** + * Function/Filter to test type of variable + * + * @param mixed $var + * @param string|null $typeTest + * @param string|null $className + * @return bool + */ + public function ofTypeFunc($var, $typeTest = null, $className = null) + { + + switch ($typeTest) { + default: + return false; + + case 'array': + return is_array($var); + + case 'bool': + return is_bool($var); + + case 'class': + return is_object($var) === true && get_class($var) === $className; + + case 'float': + return is_float($var); + + case 'int': + return is_int($var); + + case 'numeric': + return is_numeric($var); + + case 'object': + return is_object($var); + + case 'scalar': + return is_scalar($var); + + case 'string': + return is_string($var); + } + } + + /** + * @param Environment $env + * @param array $array + * @param callable|string $arrow + * @return array|CallbackFilterIterator + * @throws RuntimeError + */ + function filterFunc(Environment $env, $array, $arrow) + { + if (!$arrow instanceof \Closure && !is_string($arrow) || Utils::isDangerousFunction($arrow)) { + throw new RuntimeError('Twig |filter("' . $arrow . '") is not allowed.'); + } + + return twig_array_filter($env, $array, $arrow); + } + + /** + * @param Environment $env + * @param array $array + * @param callable|string $arrow + * @return array|CallbackFilterIterator + * @throws RuntimeError + */ + function mapFunc(Environment $env, $array, $arrow) + { + if (!$arrow instanceof \Closure && !is_string($arrow) || Utils::isDangerousFunction($arrow)) { + throw new RuntimeError('Twig |map("' . $arrow . '") is not allowed.'); + } + + return twig_array_map($env, $array, $arrow); + } + + /** + * @param Environment $env + * @param array $array + * @param callable|string $arrow + * @return array|CallbackFilterIterator + * @throws RuntimeError + */ + function reduceFunc(Environment $env, $array, $arrow) + { + if (!$arrow instanceof \Closure && !is_string($arrow) || Utils::isDangerousFunction($arrow)) { + throw new RuntimeError('Twig |reduce("' . $arrow . '") is not allowed.'); + } + + return twig_array_map($env, $array, $arrow); + } +} diff --git a/system/src/Grav/Common/Twig/Node/TwigNodeCache.php b/system/src/Grav/Common/Twig/Node/TwigNodeCache.php new file mode 100644 index 0000000..5f6fd93 --- /dev/null +++ b/system/src/Grav/Common/Twig/Node/TwigNodeCache.php @@ -0,0 +1,93 @@ + $body]; + + if ($key !== null) { + $nodes['key'] = $key; + } + + if ($lifetime !== null) { + $nodes['lifetime'] = $lifetime; + } + + parent::__construct($nodes, $defaults, $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + + + // Generate the cache key + if ($this->hasNode('key')) { + $compiler + ->write('$key = "twigcache-" . ') + ->subcompile($this->getNode('key')) + ->raw(";\n"); + } else { + $compiler + ->write('$key = ') + ->string($this->getAttribute('key')) + ->raw(";\n"); + } + + // Set the cache timeout + if ($this->hasNode('lifetime')) { + $compiler + ->write('$lifetime = ') + ->subcompile($this->getNode('lifetime')) + ->raw(";\n"); + } else { + $compiler + ->write('$lifetime = ') + ->write($this->getAttribute('lifetime')) + ->raw(";\n"); + } + + $compiler + ->write("\$cache = \\Grav\\Common\\Grav::instance()['cache'];\n") + ->write("\$cache_body = \$cache->fetch(\$key);\n") + ->write("if (\$cache_body === false) {\n") + ->indent() + ->write("\\Grav\\Common\\Grav::instance()['debugger']->addMessage(\"Cache Key: \$key, Lifetime: \$lifetime\");\n") + ->write("ob_start();\n") + ->indent() + ->subcompile($this->getNode('body')) + ->outdent() + ->write("\n") + ->write("\$cache_body = ob_get_clean();\n") + ->write("\$cache->save(\$key, \$cache_body, \$lifetime);\n") + ->outdent() + ->write("}\n") + ->write("echo '' === \$cache_body ? '' : new Markup(\$cache_body, \$this->env->getCharset());\n"); + } +} \ No newline at end of file diff --git a/system/src/Grav/Common/Twig/Node/TwigNodeLink.php b/system/src/Grav/Common/Twig/Node/TwigNodeLink.php new file mode 100644 index 0000000..46ade2c --- /dev/null +++ b/system/src/Grav/Common/Twig/Node/TwigNodeLink.php @@ -0,0 +1,114 @@ + $file, 'group' => $group, 'priority' => $priority, 'attributes' => $attributes]; + $nodes = array_filter($nodes); + + parent::__construct($nodes, ['rel' => $rel], $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Compiler $compiler A Twig Compiler instance + * @return void + * @throws LogicException + */ + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + if (!$this->hasNode('file')) { + return; + } + + $compiler->write('$attributes = [\'rel\' => \'' . $this->getAttribute('rel') . '\'];' . "\n"); + if ($this->hasNode('attributes')) { + $compiler + ->write('$attributes += ') + ->subcompile($this->getNode('attributes')) + ->raw(';' . PHP_EOL) + ->write('if (!is_array($attributes)) {' . PHP_EOL) + ->indent() + ->write("throw new UnexpectedValueException('{% {$this->tagName} with x %}: x is not an array');" . PHP_EOL) + ->outdent() + ->write('}' . PHP_EOL); + } + + if ($this->hasNode('group')) { + $compiler + ->write('$group = ') + ->subcompile($this->getNode('group')) + ->raw(';' . PHP_EOL) + ->write('if (!is_string($group)) {' . PHP_EOL) + ->indent() + ->write("throw new UnexpectedValueException('{% {$this->tagName} in x %}: x is not a string');" . PHP_EOL) + ->outdent() + ->write('}' . PHP_EOL); + } else { + $compiler->write('$group = \'head\';' . PHP_EOL); + } + + if ($this->hasNode('priority')) { + $compiler + ->write('$priority = (int)(') + ->subcompile($this->getNode('priority')) + ->raw(');' . PHP_EOL); + } else { + $compiler->write('$priority = 10;' . PHP_EOL); + } + + $compiler->write("\$assets = \\Grav\\Common\\Grav::instance()['assets'];" . PHP_EOL); + $compiler->write("\$block = \$context['block'] ?? null;" . PHP_EOL); + + $compiler + ->write('$file = (string)(') + ->subcompile($this->getNode('file')) + ->raw(');' . PHP_EOL); + + // Assets support. + $compiler->write('$assets->addLink($file, [\'group\' => $group, \'priority\' => $priority] + $attributes);' . PHP_EOL); + + // HtmlBlock support. + $compiler + ->write('if ($block instanceof \Grav\Framework\ContentBlock\HtmlBlock) {' . PHP_EOL) + ->indent() + ->write('$block->addLink([\'href\'=> $file] + $attributes, $priority, $group);' . PHP_EOL) + ->outdent() + ->write('}' . PHP_EOL); + } +} diff --git a/system/src/Grav/Common/Twig/Node/TwigNodeMarkdown.php b/system/src/Grav/Common/Twig/Node/TwigNodeMarkdown.php new file mode 100644 index 0000000..1b9dd6f --- /dev/null +++ b/system/src/Grav/Common/Twig/Node/TwigNodeMarkdown.php @@ -0,0 +1,52 @@ + $body], [], $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Compiler $compiler A Twig Compiler instance + * @return void + */ + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write('ob_start();' . PHP_EOL) + ->subcompile($this->getNode('body')) + ->write('$content = ob_get_clean();' . PHP_EOL) + ->write('preg_match("/^\s*/", $content, $matches);' . PHP_EOL) + ->write('$lines = explode("\n", $content);' . PHP_EOL) + ->write('$content = preg_replace(\'/^\' . $matches[0]. \'/\', "", $lines);' . PHP_EOL) + ->write('$content = join("\n", $content);' . PHP_EOL) + ->write('echo $this->env->getExtension(\'Grav\Common\Twig\Extension\GravExtension\')->markdownFunction($context, $content);' . PHP_EOL); + } +} diff --git a/system/src/Grav/Common/Twig/Node/TwigNodeRender.php b/system/src/Grav/Common/Twig/Node/TwigNodeRender.php new file mode 100644 index 0000000..d0285dc --- /dev/null +++ b/system/src/Grav/Common/Twig/Node/TwigNodeRender.php @@ -0,0 +1,84 @@ + $object, 'layout' => $layout, 'context' => $context]; + $nodes = array_filter($nodes); + + parent::__construct($nodes, [], $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Compiler $compiler A Twig Compiler instance + * @return void + * @throws LogicException + */ + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + $compiler->write('$object = ')->subcompile($this->getNode('object'))->raw(';' . PHP_EOL); + + if ($this->hasNode('layout')) { + $layout = $this->getNode('layout'); + $compiler->write('$layout = ')->subcompile($layout)->raw(';' . PHP_EOL); + } else { + $compiler->write('$layout = null;' . PHP_EOL); + } + + if ($this->hasNode('context')) { + $context = $this->getNode('context'); + $compiler->write('$attributes = ')->subcompile($context)->raw(';' . PHP_EOL); + } else { + $compiler->write('$attributes = null;' . PHP_EOL); + } + + $compiler + ->write('$html = $object->render($layout, $attributes ?? []);' . PHP_EOL) + ->write('$block = $context[\'block\'] ?? null;' . PHP_EOL) + ->write('if ($block instanceof \Grav\Framework\ContentBlock\ContentBlock && $html instanceof \Grav\Framework\ContentBlock\ContentBlock) {' . PHP_EOL) + ->indent() + ->write('$block->addBlock($html);' . PHP_EOL) + ->write('echo $html->getToken();' . PHP_EOL) + ->outdent() + ->write('} else {' . PHP_EOL) + ->indent() + ->write('\Grav\Common\Assets\BlockAssets::registerAssets($html);' . PHP_EOL) + ->write('echo (string)$html;' . PHP_EOL) + ->outdent() + ->write('}' . PHP_EOL) + ; + } +} diff --git a/system/src/Grav/Common/Twig/Node/TwigNodeScript.php b/system/src/Grav/Common/Twig/Node/TwigNodeScript.php new file mode 100644 index 0000000..bd80f60 --- /dev/null +++ b/system/src/Grav/Common/Twig/Node/TwigNodeScript.php @@ -0,0 +1,142 @@ + $body, 'file' => $file, 'group' => $group, 'priority' => $priority, 'attributes' => $attributes]; + $nodes = array_filter($nodes); + + parent::__construct($nodes, ['type' => $type], $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Compiler $compiler A Twig Compiler instance + * @return void + * @throws LogicException + */ + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + + if ($this->hasNode('attributes')) { + $compiler + ->write('$attributes = ') + ->subcompile($this->getNode('attributes')) + ->raw(';' . PHP_EOL) + ->write('if (!is_array($attributes)) {' . PHP_EOL) + ->indent() + ->write("throw new UnexpectedValueException('{% {$this->tagName} with x %}: x is not an array');" . PHP_EOL) + ->outdent() + ->write('}' . PHP_EOL); + } else { + $compiler->write('$attributes = [];' . PHP_EOL); + } + + if ($this->hasNode('group')) { + $compiler + ->write('$group = ') + ->subcompile($this->getNode('group')) + ->raw(';' . PHP_EOL) + ->write('if (!is_string($group)) {' . PHP_EOL) + ->indent() + ->write("throw new UnexpectedValueException('{% {$this->tagName} in x %}: x is not a string');" . PHP_EOL) + ->outdent() + ->write('}' . PHP_EOL); + } else { + $compiler->write('$group = \'head\';' . PHP_EOL); + } + + if ($this->hasNode('priority')) { + $compiler + ->write('$priority = (int)(') + ->subcompile($this->getNode('priority')) + ->raw(');' . PHP_EOL); + } else { + $compiler->write('$priority = 10;' . PHP_EOL); + } + + $compiler->write("\$assets = \\Grav\\Common\\Grav::instance()['assets'];" . PHP_EOL); + $compiler->write("\$block = \$context['block'] ?? null;" . PHP_EOL); + + if ($this->hasNode('file')) { + // JS file. + $compiler + ->write('$file = (string)(') + ->subcompile($this->getNode('file')) + ->raw(');' . PHP_EOL); + + $method = $this->getAttribute('type') === 'module' ? 'addJsModule' : 'addJs'; + + // Assets support. + $compiler->write('$assets->' . $method . '($file, [\'group\' => $group, \'priority\' => $priority] + $attributes);' . PHP_EOL); + + $method = $this->getAttribute('type') === 'module' ? 'addModule' : 'addScript'; + + // HtmlBlock support. + $compiler + ->write('if ($block instanceof \Grav\Framework\ContentBlock\HtmlBlock) {' . PHP_EOL) + ->indent() + ->write('$block->' . $method . '([\'src\'=> $file] + $attributes, $priority, $group);' . PHP_EOL) + ->outdent() + ->write('}' . PHP_EOL); + + } else { + // Inline script. + $compiler + ->write('ob_start();' . PHP_EOL) + ->subcompile($this->getNode('body')) + ->write('$content = ob_get_clean();' . PHP_EOL); + + $method = $this->getAttribute('type') === 'module' ? 'addInlineJsModule' : 'addInlineJs'; + + // Assets support. + $compiler->write('$assets->' . $method . '($content, [\'group\' => $group, \'priority\' => $priority] + $attributes);' . PHP_EOL); + + $method = $this->getAttribute('type') === 'module' ? 'addInlineModule' : 'addInlineScript'; + + // HtmlBlock support. + $compiler + ->write('if ($block instanceof \Grav\Framework\ContentBlock\HtmlBlock) {' . PHP_EOL) + ->indent() + ->write('$block->' . $method . '([\'content\'=> $content] + $attributes, $priority, $group);' . PHP_EOL) + ->outdent() + ->write('}' . PHP_EOL); + } + } +} diff --git a/system/src/Grav/Common/Twig/Node/TwigNodeStyle.php b/system/src/Grav/Common/Twig/Node/TwigNodeStyle.php new file mode 100644 index 0000000..6023375 --- /dev/null +++ b/system/src/Grav/Common/Twig/Node/TwigNodeStyle.php @@ -0,0 +1,133 @@ + $body, 'file' => $file, 'group' => $group, 'priority' => $priority, 'attributes' => $attributes]; + $nodes = array_filter($nodes); + + parent::__construct($nodes, [], $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Compiler $compiler A Twig Compiler instance + * @return void + * @throws LogicException + */ + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + + if ($this->hasNode('attributes')) { + $compiler + ->write('$attributes = ') + ->subcompile($this->getNode('attributes')) + ->raw(';' . PHP_EOL) + ->write('if (!is_array($attributes)) {' . PHP_EOL) + ->indent() + ->write("throw new UnexpectedValueException('{% {$this->tagName} with x %}: x is not an array');" . PHP_EOL) + ->outdent() + ->write('}' . PHP_EOL); + } else { + $compiler->write('$attributes = [];' . PHP_EOL); + } + + if ($this->hasNode('group')) { + $compiler + ->write('$group = ') + ->subcompile($this->getNode('group')) + ->raw(';' . PHP_EOL) + ->write('if (!is_string($group)) {' . PHP_EOL) + ->indent() + ->write("throw new UnexpectedValueException('{% {$this->tagName} in x %}: x is not a string');" . PHP_EOL) + ->outdent() + ->write('}' . PHP_EOL); + } else { + $compiler->write('$group = \'head\';' . PHP_EOL); + } + + if ($this->hasNode('priority')) { + $compiler + ->write('$priority = (int)(') + ->subcompile($this->getNode('priority')) + ->raw(');' . PHP_EOL); + } else { + $compiler->write('$priority = 10;' . PHP_EOL); + } + + $compiler->write("\$assets = \\Grav\\Common\\Grav::instance()['assets'];" . PHP_EOL); + $compiler->write("\$block = \$context['block'] ?? null;" . PHP_EOL); + + if ($this->hasNode('file')) { + // CSS file. + $compiler + ->write('$file = (string)(') + ->subcompile($this->getNode('file')) + ->raw(');' . PHP_EOL); + + // Assets support. + $compiler->write('$assets->addCss($file, [\'group\' => $group, \'priority\' => $priority] + $attributes);' . PHP_EOL); + + // HtmlBlock support. + $compiler + ->write('if ($block instanceof \Grav\Framework\ContentBlock\HtmlBlock) {' . PHP_EOL) + ->indent() + ->write('$block->addStyle([\'href\'=> $file] + $attributes, $priority, $group);' . PHP_EOL) + ->outdent() + ->write('}' . PHP_EOL); + + } else { + // Inline style. + $compiler + ->write('ob_start();' . PHP_EOL) + ->subcompile($this->getNode('body')) + ->write('$content = ob_get_clean();' . PHP_EOL); + + // Assets support. + $compiler->write('$assets->addInlineCss($content, [\'group\' => $group, \'priority\' => $priority] + $attributes);' . PHP_EOL); + + // HtmlBlock support. + $compiler + ->write('if ($block instanceof \Grav\Framework\ContentBlock\HtmlBlock) {' . PHP_EOL) + ->indent() + ->write('$block->addInlineStyle([\'content\'=> $content] + $attributes, $priority, $group);' . PHP_EOL) + ->outdent() + ->write('}' . PHP_EOL); + } + } +} diff --git a/system/src/Grav/Common/Twig/Node/TwigNodeSwitch.php b/system/src/Grav/Common/Twig/Node/TwigNodeSwitch.php new file mode 100644 index 0000000..d48f9b1 --- /dev/null +++ b/system/src/Grav/Common/Twig/Node/TwigNodeSwitch.php @@ -0,0 +1,88 @@ + $value, 'cases' => $cases, 'default' => $default]; + $nodes = array_filter($nodes); + + parent::__construct($nodes, [], $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Compiler $compiler A Twig Compiler instance + * @return void + */ + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write('switch (') + ->subcompile($this->getNode('value')) + ->raw(") {\n") + ->indent(); + + /** @var Node $case */ + foreach ($this->getNode('cases') as $case) { + if (!$case->hasNode('body')) { + continue; + } + + foreach ($case->getNode('values') as $value) { + $compiler + ->write('case ') + ->subcompile($value) + ->raw(":\n"); + } + + $compiler + ->write("{\n") + ->indent() + ->subcompile($case->getNode('body')) + ->write("break;\n") + ->outdent() + ->write("}\n"); + } + + if ($this->hasNode('default')) { + $compiler + ->write("default:\n") + ->write("{\n") + ->indent() + ->subcompile($this->getNode('default')) + ->outdent() + ->write("}\n"); + } + + $compiler + ->outdent() + ->write("}\n"); + } +} diff --git a/system/src/Grav/Common/Twig/Node/TwigNodeThrow.php b/system/src/Grav/Common/Twig/Node/TwigNodeThrow.php new file mode 100644 index 0000000..6a0ce85 --- /dev/null +++ b/system/src/Grav/Common/Twig/Node/TwigNodeThrow.php @@ -0,0 +1,52 @@ + $message], ['code' => $code], $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Compiler $compiler A Twig Compiler instance + * @return void + * @throws LogicException + */ + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + + $compiler + ->write('throw new \Grav\Common\Twig\Exception\TwigException(') + ->subcompile($this->getNode('message')) + ->write(', ') + ->write($this->getAttribute('code') ?: 500) + ->write(");\n"); + } +} diff --git a/system/src/Grav/Common/Twig/Node/TwigNodeTryCatch.php b/system/src/Grav/Common/Twig/Node/TwigNodeTryCatch.php new file mode 100644 index 0000000..bc93d6c --- /dev/null +++ b/system/src/Grav/Common/Twig/Node/TwigNodeTryCatch.php @@ -0,0 +1,67 @@ + $try, 'catch' => $catch]; + $nodes = array_filter($nodes); + + parent::__construct($nodes, [], $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Compiler $compiler A Twig Compiler instance + * @return void + * @throws LogicException + */ + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + + $compiler->write('try {'); + + $compiler + ->indent() + ->subcompile($this->getNode('try')) + ->outdent() + ->write('} catch (\Exception $e) {' . "\n") + ->indent() + ->write('if (isset($context[\'grav\'][\'debugger\'])) $context[\'grav\'][\'debugger\']->addException($e);' . "\n") + ->write('$context[\'e\'] = $e;' . "\n"); + + if ($this->hasNode('catch')) { + $compiler->subcompile($this->getNode('catch')); + } + + $compiler + ->outdent() + ->write("}\n"); + } +} diff --git a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserCache.php b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserCache.php new file mode 100644 index 0000000..f4986e8 --- /dev/null +++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserCache.php @@ -0,0 +1,74 @@ +parser->getStream(); + $lineno = $token->getLine(); + + // Parse the optional key and timeout parameters + $defaults = [ + 'key' => $this->parser->getVarName() . $lineno, + 'lifetime' => Grav::instance()['cache']->getLifetime() + ]; + + $key = null; + $lifetime = null; + while (!$stream->test(Token::BLOCK_END_TYPE)) { + if ($stream->test(Token::STRING_TYPE)) { + $key = $this->parser->getExpressionParser()->parseExpression(); + } elseif ($stream->test(Token::NUMBER_TYPE)) { + $lifetime = $this->parser->getExpressionParser()->parseExpression(); + } else { + throw new \Twig\Error\SyntaxError("Unexpected token type in cache tag.", $token->getLine(), $stream->getSourceContext()); + } + } + + $stream->expect(Token::BLOCK_END_TYPE); + + // Parse the content inside the cache block + $body = $this->parser->subparse([$this, 'decideCacheEnd'], true); + + $stream->expect(Token::BLOCK_END_TYPE); + + return new TwigNodeCache($body, $key, $lifetime, $defaults, $lineno, $this->getTag()); + } + + public function decideCacheEnd(Token $token): bool + { + return $token->test('endcache'); + } + + public function getTag(): string + { + return 'cache'; + } +} \ No newline at end of file diff --git a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserLink.php b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserLink.php new file mode 100644 index 0000000..77cd5a6 --- /dev/null +++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserLink.php @@ -0,0 +1,109 @@ +getLine(); + + [$rel, $file, $group, $priority, $attributes] = $this->parseArguments($token); + + return new TwigNodeLink($rel, $file, $group, $priority, $attributes, $lineno, $this->getTag()); + } + + /** + * @param Token $token + * @return array + */ + protected function parseArguments(Token $token): array + { + $stream = $this->parser->getStream(); + + + $rel = null; + if ($stream->test(Token::NAME_TYPE, $this->rel)) { + $rel = $stream->getCurrent()->getValue(); + $stream->next(); + } + + $file = null; + if (!$stream->test(Token::NAME_TYPE) && !$stream->test(Token::BLOCK_END_TYPE)) { + $file = $this->parser->getExpressionParser()->parseExpression(); + } + + $group = null; + if ($stream->nextIf(Token::NAME_TYPE, 'at')) { + $group = $this->parser->getExpressionParser()->parseExpression(); + } + + $priority = null; + if ($stream->nextIf(Token::NAME_TYPE, 'priority')) { + $stream->expect(Token::PUNCTUATION_TYPE, ':'); + $priority = $this->parser->getExpressionParser()->parseExpression(); + } + + $attributes = null; + if ($stream->nextIf(Token::NAME_TYPE, 'with')) { + $attributes = $this->parser->getExpressionParser()->parseExpression(); + } + + $stream->expect(Token::BLOCK_END_TYPE); + + return [$rel, $file, $group, $priority, $attributes]; + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag(): string + { + return 'link'; + } +} diff --git a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserMarkdown.php b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserMarkdown.php new file mode 100644 index 0000000..c1943d8 --- /dev/null +++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserMarkdown.php @@ -0,0 +1,59 @@ +getLine(); + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + $body = $this->parser->subparse([$this, 'decideMarkdownEnd'], true); + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + return new TwigNodeMarkdown($body, $lineno, $this->getTag()); + } + /** + * Decide if current token marks end of Markdown block. + * + * @param Token $token + * @return bool + */ + public function decideMarkdownEnd(Token $token): bool + { + return $token->test('endmarkdown'); + } + /** + * {@inheritdoc} + */ + public function getTag(): string + { + return 'markdown'; + } +} diff --git a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserRender.php b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserRender.php new file mode 100644 index 0000000..5e73e91 --- /dev/null +++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserRender.php @@ -0,0 +1,74 @@ +getLine(); + + [$object, $layout, $context] = $this->parseArguments($token); + + return new TwigNodeRender($object, $layout, $context, $lineno, $this->getTag()); + } + + /** + * @param Token $token + * @return array + */ + protected function parseArguments(Token $token): array + { + $stream = $this->parser->getStream(); + + $object = $this->parser->getExpressionParser()->parseExpression(); + + $layout = null; + if ($stream->nextIf(Token::NAME_TYPE, 'layout')) { + $stream->expect(Token::PUNCTUATION_TYPE, ':'); + $layout = $this->parser->getExpressionParser()->parseExpression(); + } + + $context = null; + if ($stream->nextIf(Token::NAME_TYPE, 'with')) { + $context = $this->parser->getExpressionParser()->parseExpression(); + } + + $stream->expect(Token::BLOCK_END_TYPE); + + return [$object, $layout, $context]; + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag(): string + { + return 'render'; + } +} diff --git a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserScript.php b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserScript.php new file mode 100644 index 0000000..f575f04 --- /dev/null +++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserScript.php @@ -0,0 +1,132 @@ +getLine(); + $stream = $this->parser->getStream(); + + [$type, $file, $group, $priority, $attributes] = $this->parseArguments($token); + + $content = null; + if ($file === null) { + $content = $this->parser->subparse([$this, 'decideBlockEnd'], true); + $stream->expect(Token::BLOCK_END_TYPE); + } + + return new TwigNodeScript($content, $type, $file, $group, $priority, $attributes, $lineno, $this->getTag()); + } + + /** + * @param Token $token + * @return array + */ + protected function parseArguments(Token $token): array + { + $stream = $this->parser->getStream(); + + // Look for deprecated {% script ... in ... %} + if (!$stream->test(Token::BLOCK_END_TYPE) && !$stream->test(Token::OPERATOR_TYPE, 'in')) { + $i = 0; + do { + $token = $stream->look(++$i); + if ($token->test(Token::BLOCK_END_TYPE)) { + break; + } + if ($token->test(Token::OPERATOR_TYPE, 'in') && $stream->look($i+1)->test(Token::STRING_TYPE)) { + user_error("Twig: Using {% script ... in ... %} is deprecated, use {% script ... at ... %} instead", E_USER_DEPRECATED); + + break; + } + } while (true); + } + + $type = null; + if ($stream->test(Token::NAME_TYPE, 'module')) { + $type = $stream->getCurrent()->getValue(); + $stream->next(); + } + + $file = null; + if (!$stream->test(Token::NAME_TYPE) && !$stream->test(Token::OPERATOR_TYPE, 'in') && !$stream->test(Token::BLOCK_END_TYPE)) { + $file = $this->parser->getExpressionParser()->parseExpression(); + } + + $group = null; + if ($stream->nextIf(Token::NAME_TYPE, 'at') || $stream->nextIf(Token::OPERATOR_TYPE, 'in')) { + $group = $this->parser->getExpressionParser()->parseExpression(); + } + + $priority = null; + if ($stream->nextIf(Token::NAME_TYPE, 'priority')) { + $stream->expect(Token::PUNCTUATION_TYPE, ':'); + $priority = $this->parser->getExpressionParser()->parseExpression(); + } + + $attributes = null; + if ($stream->nextIf(Token::NAME_TYPE, 'with')) { + $attributes = $this->parser->getExpressionParser()->parseExpression(); + } + + $stream->expect(Token::BLOCK_END_TYPE); + + return [$type, $file, $group, $priority, $attributes]; + } + + /** + * @param Token $token + * @return bool + */ + public function decideBlockEnd(Token $token): bool + { + return $token->test('endscript'); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag(): string + { + return 'script'; + } +} diff --git a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserStyle.php b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserStyle.php new file mode 100644 index 0000000..df5fb81 --- /dev/null +++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserStyle.php @@ -0,0 +1,119 @@ +getLine(); + $stream = $this->parser->getStream(); + + [$file, $group, $priority, $attributes] = $this->parseArguments($token); + + $content = null; + if (!$file) { + $content = $this->parser->subparse([$this, 'decideBlockEnd'], true); + $stream->expect(Token::BLOCK_END_TYPE); + } + + return new TwigNodeStyle($content, $file, $group, $priority, $attributes, $lineno, $this->getTag()); + } + + /** + * @param Token $token + * @return array + */ + protected function parseArguments(Token $token): array + { + $stream = $this->parser->getStream(); + + // Look for deprecated {% style ... in ... %} + if (!$stream->test(Token::BLOCK_END_TYPE) && !$stream->test(Token::OPERATOR_TYPE, 'in')) { + $i = 0; + do { + $token = $stream->look(++$i); + if ($token->test(Token::BLOCK_END_TYPE)) { + break; + } + if ($token->test(Token::OPERATOR_TYPE, 'in') && $stream->look($i+1)->test(Token::STRING_TYPE)) { + user_error("Twig: Using {% style ... in ... %} is deprecated, use {% style ... at ... %} instead", E_USER_DEPRECATED); + + break; + } + } while (true); + } + + $file = null; + if (!$stream->test(Token::NAME_TYPE) && !$stream->test(Token::OPERATOR_TYPE, 'in') && !$stream->test(Token::BLOCK_END_TYPE)) { + $file = $this->parser->getExpressionParser()->parseExpression(); + } + + $group = null; + if ($stream->nextIf(Token::NAME_TYPE, 'at') || $stream->nextIf(Token::OPERATOR_TYPE, 'in')) { + $group = $this->parser->getExpressionParser()->parseExpression(); + } + + $priority = null; + if ($stream->nextIf(Token::NAME_TYPE, 'priority')) { + $stream->expect(Token::PUNCTUATION_TYPE, ':'); + $priority = $this->parser->getExpressionParser()->parseExpression(); + } + + $attributes = null; + if ($stream->nextIf(Token::NAME_TYPE, 'with')) { + $attributes = $this->parser->getExpressionParser()->parseExpression(); + } + + $stream->expect(Token::BLOCK_END_TYPE); + + return [$file, $group, $priority, $attributes]; + } + + /** + * @param Token $token + * @return bool + */ + public function decideBlockEnd(Token $token): bool + { + return $token->test('endstyle'); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag(): string + { + return 'style'; + } +} diff --git a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserSwitch.php b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserSwitch.php new file mode 100644 index 0000000..e5b3d40 --- /dev/null +++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserSwitch.php @@ -0,0 +1,132 @@ +getLine(); + $stream = $this->parser->getStream(); + + $name = $this->parser->getExpressionParser()->parseExpression(); + $stream->expect(Token::BLOCK_END_TYPE); + + // There can be some whitespace between the {% switch %} and first {% case %} tag. + while ($stream->getCurrent()->getType() === Token::TEXT_TYPE && trim($stream->getCurrent()->getValue()) === '') { + $stream->next(); + } + + $stream->expect(Token::BLOCK_START_TYPE); + + $expressionParser = $this->parser->getExpressionParser(); + + $default = null; + $cases = []; + $end = false; + + while (!$end) { + $next = $stream->next(); + + switch ($next->getValue()) { + case 'case': + $values = []; + + while (true) { + $values[] = $expressionParser->parsePrimaryExpression(); + // Multiple allowed values? + if ($stream->test(Token::OPERATOR_TYPE, 'or')) { + $stream->next(); + } else { + break; + } + } + + $stream->expect(Token::BLOCK_END_TYPE); + $body = $this->parser->subparse([$this, 'decideIfFork']); + $cases[] = new Node([ + 'values' => new Node($values), + 'body' => $body + ]); + break; + + case 'default': + $stream->expect(Token::BLOCK_END_TYPE); + $default = $this->parser->subparse([$this, 'decideIfEnd']); + break; + + case 'endswitch': + $end = true; + break; + + default: + throw new SyntaxError(sprintf('Unexpected end of template. Twig was looking for the following tags "case", "default", or "endswitch" to close the "switch" block started at line %d)', $lineno), -1); + } + } + + $stream->expect(Token::BLOCK_END_TYPE); + + return new TwigNodeSwitch($name, new Node($cases), $default, $lineno, $this->getTag()); + } + + /** + * Decide if current token marks switch logic. + * + * @param Token $token + * @return bool + */ + public function decideIfFork(Token $token): bool + { + return $token->test(['case', 'default', 'endswitch']); + } + + /** + * Decide if current token marks end of swtich block. + * + * @param Token $token + * @return bool + */ + public function decideIfEnd(Token $token): bool + { + return $token->test(['endswitch']); + } + + /** + * {@inheritdoc} + */ + public function getTag(): string + { + return 'switch'; + } +} diff --git a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserThrow.php b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserThrow.php new file mode 100644 index 0000000..63527ca --- /dev/null +++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserThrow.php @@ -0,0 +1,55 @@ + + * {% throw 404 'Not Found' %} + * + */ +class TwigTokenParserThrow extends AbstractTokenParser +{ + /** + * Parses a token and returns a node. + * + * @param Token $token + * @return TwigNodeThrow + * @throws SyntaxError + */ + public function parse(Token $token) + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + + $code = $stream->expect(Token::NUMBER_TYPE)->getValue(); + $message = $this->parser->getExpressionParser()->parseExpression(); + $stream->expect(Token::BLOCK_END_TYPE); + + return new TwigNodeThrow((int)$code, $message, $lineno, $this->getTag()); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag(): string + { + return 'throw'; + } +} diff --git a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserTryCatch.php b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserTryCatch.php new file mode 100644 index 0000000..0cce083 --- /dev/null +++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserTryCatch.php @@ -0,0 +1,81 @@ + + * {% try %} + *
  • {{ user.get('name') }}
  • + * {% catch %} + * {{ e.message }} + * {% endcatch %} + * + */ +class TwigTokenParserTryCatch extends AbstractTokenParser +{ + /** + * Parses a token and returns a node. + * + * @param Token $token + * @return TwigNodeTryCatch + * @throws SyntaxError + */ + public function parse(Token $token) + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + + $stream->expect(Token::BLOCK_END_TYPE); + $try = $this->parser->subparse([$this, 'decideCatch']); + $stream->next(); + $stream->expect(Token::BLOCK_END_TYPE); + $catch = $this->parser->subparse([$this, 'decideEnd']); + $stream->next(); + $stream->expect(Token::BLOCK_END_TYPE); + + return new TwigNodeTryCatch($try, $catch, $lineno, $this->getTag()); + } + + /** + * @param Token $token + * @return bool + */ + public function decideCatch(Token $token): bool + { + return $token->test(['catch']); + } + + /** + * @param Token $token + * @return bool + */ + public function decideEnd(Token $token): bool + { + return $token->test(['endtry']) || $token->test(['endcatch']); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag(): string + { + return 'try'; + } +} diff --git a/system/src/Grav/Common/Twig/Twig.php b/system/src/Grav/Common/Twig/Twig.php new file mode 100644 index 0000000..eabc419 --- /dev/null +++ b/system/src/Grav/Common/Twig/Twig.php @@ -0,0 +1,578 @@ +grav = $grav; + $this->twig_paths = []; + } + + /** + * Twig initialization that sets the twig loader chain, then the environment, then extensions + * and also the base set of twig vars + * + * @return $this + */ + public function init() + { + if (null === $this->twig) { + /** @var Config $config */ + $config = $this->grav['config']; + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + /** @var Language $language */ + $language = $this->grav['language']; + + $active_language = $language->getActive(); + + // handle language templates if available + if ($language->enabled()) { + $lang_templates = $locator->findResource('theme://templates/' . ($active_language ?: $language->getDefault())); + if ($lang_templates) { + $this->twig_paths[] = $lang_templates; + } + } + + $this->twig_paths = array_merge($this->twig_paths, $locator->findResources('theme://templates')); + + $this->grav->fireEvent('onTwigTemplatePaths'); + + // Add Grav core templates location + $core_templates = array_merge($locator->findResources('system://templates'), $locator->findResources('system://templates/testing')); + $this->twig_paths = array_merge($this->twig_paths, $core_templates); + + $this->loader = new FilesystemLoader($this->twig_paths); + + // Register all other prefixes as namespaces in twig + foreach ($locator->getPaths('theme') as $prefix => $_) { + if ($prefix === '') { + continue; + } + + $twig_paths = []; + + // handle language templates if available + if ($language->enabled()) { + $lang_templates = $locator->findResource('theme://'.$prefix.'templates/' . ($active_language ?: $language->getDefault())); + if ($lang_templates) { + $twig_paths[] = $lang_templates; + } + } + + $twig_paths = array_merge($twig_paths, $locator->findResources('theme://'.$prefix.'templates')); + + $namespace = trim($prefix, '/'); + $this->loader->setPaths($twig_paths, $namespace); + } + + $this->grav->fireEvent('onTwigLoader'); + + $this->loaderArray = new ArrayLoader([]); + $loader_chain = new ChainLoader([$this->loaderArray, $this->loader]); + + $params = $config->get('system.twig'); + if (!empty($params['cache'])) { + $cachePath = $locator->findResource('cache://twig', true, true); + $params['cache'] = new FilesystemCache($cachePath, FilesystemCache::FORCE_BYTECODE_INVALIDATION); + } + + if (!$config->get('system.strict_mode.twig_compat', false)) { + // Force autoescape on for all files if in strict mode. + $params['autoescape'] = 'html'; + } elseif (!empty($this->autoescape)) { + $params['autoescape'] = $this->autoescape ? 'html' : false; + } + + if (empty($params['autoescape'])) { + user_error('Grav 2.0 will have Twig auto-escaping forced on (can be emulated by turning off \'system.strict_mode.twig_compat\' setting in your configuration)', E_USER_DEPRECATED); + } + + $this->twig = new TwigEnvironment($loader_chain, $params); + + $this->twig->registerUndefinedFunctionCallback(function (string $name) use ($config) { + $allowed = $config->get('system.twig.safe_functions'); + if (is_array($allowed) && in_array($name, $allowed, true) && function_exists($name)) { + return new TwigFunction($name, $name); + } + if ($config->get('system.twig.undefined_functions')) { + if (function_exists($name)) { + if (!Utils::isDangerousFunction($name)) { + user_error("PHP function {$name}() was used as Twig function. This is deprecated in Grav 1.7. Please add it to system configuration: `system.twig.safe_functions`", E_USER_DEPRECATED); + + return new TwigFunction($name, $name); + } + + /** @var Debugger $debugger */ + $debugger = $this->grav['debugger']; + $debugger->addException(new RuntimeException("Blocked potentially dangerous PHP function {$name}() being used as Twig function. If you really want to use it, please add it to system configuration: `system.twig.safe_functions`")); + } + + return new TwigFunction($name, static function () {}); + } + + return false; + }); + + $this->twig->registerUndefinedFilterCallback(function (string $name) use ($config) { + $allowed = $config->get('system.twig.safe_filters'); + if (is_array($allowed) && in_array($name, $allowed, true) && function_exists($name)) { + return new TwigFilter($name, $name); + } + if ($config->get('system.twig.undefined_filters')) { + if (function_exists($name)) { + if (!Utils::isDangerousFunction($name)) { + user_error("PHP function {$name}() used as Twig filter. This is deprecated in Grav 1.7. Please add it to system configuration: `system.twig.safe_filters`", E_USER_DEPRECATED); + + return new TwigFilter($name, $name); + } + + /** @var Debugger $debugger */ + $debugger = $this->grav['debugger']; + $debugger->addException(new RuntimeException("Blocked potentially dangerous PHP function {$name}() being used as Twig filter. If you really want to use it, please add it to system configuration: `system.twig.safe_filters`")); + } + + return new TwigFilter($name, static function () {}); + } + + return false; + }); + + $this->grav->fireEvent('onTwigInitialized'); + + // set default date format if set in config + if ($config->get('system.pages.dateformat.long')) { + /** @var CoreExtension $extension */ + $extension = $this->twig->getExtension(CoreExtension::class); + $extension->setDateFormat($config->get('system.pages.dateformat.long')); + } + // enable the debug extension if required + if ($config->get('system.twig.debug')) { + $this->twig->addExtension(new DebugExtension()); + } + $this->twig->addExtension(new GravExtension()); + $this->twig->addExtension(new FilesystemExtension()); + $this->twig->addExtension(new DeferredExtension()); + $this->twig->addExtension(new StringLoaderExtension()); + + /** @var Debugger $debugger */ + $debugger = $this->grav['debugger']; + $debugger->addTwigProfiler($this->twig); + + $this->grav->fireEvent('onTwigExtensions'); + + /** @var Pages $pages */ + $pages = $this->grav['pages']; + + // Set some standard variables for twig + $this->twig_vars += [ + 'config' => $config, + 'system' => $config->get('system'), + 'theme' => $config->get('theme'), + 'site' => $config->get('site'), + 'uri' => $this->grav['uri'], + 'assets' => $this->grav['assets'], + 'taxonomy' => $this->grav['taxonomy'], + 'browser' => $this->grav['browser'], + 'base_dir' => GRAV_ROOT, + 'home_url' => $pages->homeUrl($active_language), + 'base_url' => $pages->baseUrl($active_language), + 'base_url_absolute' => $pages->baseUrl($active_language, true), + 'base_url_relative' => $pages->baseUrl($active_language, false), + 'base_url_simple' => $this->grav['base_url'], + 'theme_dir' => $locator->findResource('theme://'), + 'theme_url' => $this->grav['base_url'] . '/' . $locator->findResource('theme://', false), + 'html_lang' => $this->grav['language']->getActive() ?: $config->get('site.default_lang', 'en'), + 'language_codes' => new LanguageCodes, + ]; + } + + return $this; + } + + /** + * @return Environment + */ + public function twig() + { + return $this->twig; + } + + /** + * @return FilesystemLoader + */ + public function loader() + { + return $this->loader; + } + + /** + * @return Profile + */ + public function profile() + { + return $this->profile; + } + + + /** + * Adds or overrides a template. + * + * @param string $name The template name + * @param string $template The template source + */ + public function setTemplate($name, $template) + { + $this->loaderArray->setTemplate($name, $template); + } + + /** + * Twig process that renders a page item. It supports two variations: + * 1) Handles modular pages by rendering a specific page based on its modular twig template + * 2) Renders individual page items for twig processing before the site rendering + * + * @param PageInterface $item The page item to render + * @param string|null $content Optional content override + * + * @return string The rendered output + */ + public function processPage(PageInterface $item, $content = null) + { + $content = $content ?? $item->content(); + $content = Security::cleanDangerousTwig($content); + + // override the twig header vars for local resolution + $this->grav->fireEvent('onTwigPageVariables', new Event(['page' => $item])); + $twig_vars = $this->twig_vars; + + $twig_vars['page'] = $item; + $twig_vars['media'] = $item->media(); + $twig_vars['header'] = $item->header(); + $local_twig = clone $this->twig; + + $output = ''; + + try { + if ($item->isModule()) { + $twig_vars['content'] = $content; + $template = $this->getPageTwigTemplate($item); + $output = $content = $local_twig->render($template, $twig_vars); + } + + // Process in-page Twig + if ($item->shouldProcess('twig')) { + $name = '@Page:' . $item->path(); + $this->setTemplate($name, $content); + $output = $local_twig->render($name, $twig_vars); + } + + } catch (LoaderError $e) { + throw new RuntimeException($e->getRawMessage(), 400, $e); + } + + return $output; + } + + /** + * Process a Twig template directly by using a template name + * and optional array of variables + * + * @param string $template template to render with + * @param array $vars Optional variables + * + * @return string + */ + public function processTemplate($template, $vars = []) + { + // override the twig header vars for local resolution + $this->grav->fireEvent('onTwigTemplateVariables'); + $vars += $this->twig_vars; + + try { + $output = $this->twig->render($template, $vars); + } catch (LoaderError $e) { + throw new RuntimeException($e->getRawMessage(), 404, $e); + } + + return $output; + } + + + /** + * Process a Twig template directly by using a Twig string + * and optional array of variables + * + * @param string $string string to render. + * @param array $vars Optional variables + * + * @return string + */ + public function processString($string, array $vars = []) + { + // override the twig header vars for local resolution + $this->grav->fireEvent('onTwigStringVariables'); + $vars += $this->twig_vars; + + $string = Security::cleanDangerousTwig($string); + + $name = '@Var:' . $string; + $this->setTemplate($name, $string); + + try { + $output = $this->twig->render($name, $vars); + } catch (LoaderError $e) { + throw new RuntimeException($e->getRawMessage(), 404, $e); + } + + return $output; + } + + /** + * Twig process that renders the site layout. This is the main twig process that renders the overall + * page and handles all the layout for the site display. + * + * @param string|null $format Output format (defaults to HTML). + * @param array $vars + * @return string the rendered output + * @throws RuntimeException + */ + public function processSite($format = null, array $vars = []) + { + try { + $grav = $this->grav; + + // set the page now it's been processed + $grav->fireEvent('onTwigSiteVariables'); + + /** @var Pages $pages */ + $pages = $grav['pages']; + + /** @var PageInterface $page */ + $page = $grav['page']; + + $content = Security::cleanDangerousTwig($page->content()); + + $twig_vars = $this->twig_vars; + $twig_vars['theme'] = $grav['config']->get('theme'); + $twig_vars['pages'] = $pages->root(); + $twig_vars['page'] = $page; + $twig_vars['header'] = $page->header(); + $twig_vars['media'] = $page->media(); + $twig_vars['content'] = $content; + + // determine if params are set, if so disable twig cache + $params = $grav['uri']->params(null, true); + if (!empty($params)) { + $this->twig->setCache(false); + } + + // Get Twig template layout + $template = $this->getPageTwigTemplate($page, $format); + $page->templateFormat($format); + + $output = $this->twig->render($template, $vars + $twig_vars); + } catch (LoaderError $e) { + throw new RuntimeException($e->getMessage(), 400, $e); + } catch (RuntimeError $e) { + $prev = $e->getPrevious(); + if ($prev instanceof TwigException) { + $code = $prev->getCode() ?: 500; + // Fire onPageNotFound event. + $event = new Event([ + 'page' => $page, + 'code' => $code, + 'message' => $prev->getMessage(), + 'exception' => $prev, + 'route' => $grav['route'], + 'request' => $grav['request'] + ]); + $event = $grav->fireEvent("onDisplayErrorPage.{$code}", $event); + $newPage = $event['page']; + if ($newPage && $newPage !== $page) { + unset($grav['page']); + $grav['page'] = $newPage; + + return $this->processSite($newPage->templateFormat(), $vars); + } + } + + throw $e; + } + + return $output; + } + + /** + * Wraps the FilesystemLoader addPath method (should be used only in `onTwigLoader()` event + * @param string $template_path + * @param string $namespace + * @throws LoaderError + */ + public function addPath($template_path, $namespace = '__main__') + { + $this->loader->addPath($template_path, $namespace); + } + + /** + * Wraps the FilesystemLoader prependPath method (should be used only in `onTwigLoader()` event + * @param string $template_path + * @param string $namespace + * @throws LoaderError + */ + public function prependPath($template_path, $namespace = '__main__') + { + $this->loader->prependPath($template_path, $namespace); + } + + /** + * Simple helper method to get the twig template if it has already been set, else return + * the one being passed in + * NOTE: Modular pages that are injected should not use this pre-set template as it's usually set at the page level + * + * @param string $template the template name + * @return string the template name + */ + public function template(string $template): string + { + if (isset($this->template)) { + $template = $this->template; + unset($this->template); + } + + return $template; + } + + /** + * @param PageInterface $page + * @param string|null $format + * @return string + */ + public function getPageTwigTemplate($page, &$format = null) + { + $template = $page->template(); + $default = $page->isModule() ? 'modular/default' : 'default'; + $extension = $format ?: $page->templateFormat(); + $twig_extension = $extension ? '.'. $extension .TWIG_EXT : TEMPLATE_EXT; + $template_file = $this->template($template . $twig_extension); + + // TODO: no longer needed in Twig 3. + /** @var ExistsLoaderInterface $loader */ + $loader = $this->twig->getLoader(); + if ($loader->exists($template_file)) { + // template.xxx.twig + $page_template = $template_file; + } elseif ($twig_extension !== TEMPLATE_EXT && $loader->exists($template . TEMPLATE_EXT)) { + // template.html.twig + $page_template = $template . TEMPLATE_EXT; + $format = 'html'; + } elseif ($loader->exists($default . $twig_extension)) { + // default.xxx.twig + $page_template = $default . $twig_extension; + } else { + // default.html.twig + $page_template = $default . TEMPLATE_EXT; + $format = 'html'; + } + + return $page_template; + + } + + /** + * Overrides the autoescape setting + * + * @param bool $state + * @return void + * @deprecated 1.5 Auto-escape should always be turned on to protect against XSS issues (can be disabled per template file). + */ + public function setAutoescape($state) + { + if (!$state) { + user_error(__CLASS__ . '::' . __FUNCTION__ . '(false) is deprecated since Grav 1.5', E_USER_DEPRECATED); + } + + $this->autoescape = (bool) $state; + } + +} diff --git a/system/src/Grav/Common/Twig/TwigClockworkDataSource.php b/system/src/Grav/Common/Twig/TwigClockworkDataSource.php new file mode 100644 index 0000000..7d85b7e --- /dev/null +++ b/system/src/Grav/Common/Twig/TwigClockworkDataSource.php @@ -0,0 +1,58 @@ +twig = $twig; + } + + /** + * Register the Twig profiler extension + */ + public function listenToEvents(): void + { + $this->twig->addExtension(new ProfilerExtension($this->profile = new Profile())); + } + + /** + * Adds rendered views to the request + * + * @param Request $request + * @return Request + */ + public function resolve(Request $request) + { + $timeline = (new TwigClockworkDumper())->dump($this->profile); + + $request->viewsData = array_merge($request->viewsData, $timeline->finalize()); + + return $request; + } +} diff --git a/system/src/Grav/Common/Twig/TwigClockworkDumper.php b/system/src/Grav/Common/Twig/TwigClockworkDumper.php new file mode 100644 index 0000000..63c361e --- /dev/null +++ b/system/src/Grav/Common/Twig/TwigClockworkDumper.php @@ -0,0 +1,72 @@ +dumpProfile($profile, $timeline); + + return $timeline; + } + + /** + * @param Profile $profile + * @param Timeline $timeline + * @param null $parent + */ + public function dumpProfile(Profile $profile, Timeline $timeline, $parent = null) + { + $id = $this->lastId++; + + if ($profile->isRoot()) { + $name = $profile->getName(); + } elseif ($profile->isTemplate()) { + $name = $profile->getTemplate(); + } else { + $name = $profile->getTemplate() . '::' . $profile->getType() . '(' . $profile->getName() . ')'; + } + + foreach ($profile as $p) { + $this->dumpProfile($p, $timeline, $id); + } + + $data = $profile->__serialize(); + + $timeline->event($name, [ + 'name' => $id, + 'start' => $data[3]['wt'] ?? null, + 'end' => $data[4]['wt'] ?? null, + 'data' => [ + 'data' => [], + 'memoryUsage' => $data[4]['mu'] ?? null, + 'parent' => $parent + ] + ]); + } +} diff --git a/system/src/Grav/Common/Twig/TwigEnvironment.php b/system/src/Grav/Common/Twig/TwigEnvironment.php new file mode 100644 index 0000000..3ede6ef --- /dev/null +++ b/system/src/Grav/Common/Twig/TwigEnvironment.php @@ -0,0 +1,60 @@ +getLoader(); + if (!$loader->exists($name)) { + continue; + } + } + + // Throws LoaderError: Unable to find template "%s". + return $this->loadTemplate($name); + } + + throw new LoaderError(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names))); + } +} diff --git a/system/src/Grav/Common/Twig/TwigExtension.php b/system/src/Grav/Common/Twig/TwigExtension.php new file mode 100644 index 0000000..2bd1e73 --- /dev/null +++ b/system/src/Grav/Common/Twig/TwigExtension.php @@ -0,0 +1,21 @@ +get('system.twig.umask_fix', false); + } + + if (self::$umask) { + $dir = dirname($file); + if (!is_dir($dir)) { + $old = umask(0002); + Folder::create($dir); + umask($old); + } + parent::writeCacheFile($file, $content); + chmod($file, 0775); + } else { + parent::writeCacheFile($file, $content); + } + } +} diff --git a/system/src/Grav/Common/Uri.php b/system/src/Grav/Common/Uri.php new file mode 100644 index 0000000..ccf6118 --- /dev/null +++ b/system/src/Grav/Common/Uri.php @@ -0,0 +1,1527 @@ +createFromString($env); + } else { + $this->createFromEnvironment(is_array($env) ? $env : $_SERVER); + } + } + + /** + * Initialize the URI class with a url passed via parameter. + * Used for testing purposes. + * + * @param string $url the URL to use in the class + * @return $this + */ + public function initializeWithUrl($url = '') + { + if ($url) { + $this->createFromString($url); + } + + return $this; + } + + /** + * Initialize the URI class by providing url and root_path arguments + * + * @param string $url + * @param string $root_path + * @return $this + */ + public function initializeWithUrlAndRootPath($url, $root_path) + { + $this->initializeWithUrl($url); + $this->root_path = $root_path; + + return $this; + } + + /** + * Validate a hostname + * + * @param string $hostname The hostname + * @return bool + */ + public function validateHostname($hostname) + { + return (bool)preg_match(static::HOSTNAME_REGEX, $hostname); + } + + /** + * Initializes the URI object based on the url set on the object + * + * @return void + */ + public function init() + { + $grav = Grav::instance(); + + /** @var Config $config */ + $config = $grav['config']; + + /** @var Language $language */ + $language = $grav['language']; + + // add the port to the base for non-standard ports + if ($this->port && $config->get('system.reverse_proxy_setup') === false) { + $this->base .= ':' . $this->port; + } + + // Handle custom base + $custom_base = rtrim($grav['config']->get('system.custom_base_url', ''), '/'); + if ($custom_base) { + $custom_parts = parse_url($custom_base); + if ($custom_parts === false) { + throw new RuntimeException('Bad configuration: system.custom_base_url'); + } + $orig_root_path = $this->root_path; + $this->root_path = isset($custom_parts['path']) ? rtrim($custom_parts['path'], '/') : ''; + if (isset($custom_parts['scheme'])) { + $this->base = $custom_parts['scheme'] . '://' . $custom_parts['host']; + $this->port = $custom_parts['port'] ?? null; + if ($this->port && $config->get('system.reverse_proxy_setup') === false) { + $this->base .= ':' . $this->port; + } + $this->root = $custom_base; + } else { + $this->root = $this->base . $this->root_path; + } + $this->uri = Utils::replaceFirstOccurrence($orig_root_path, $this->root_path, $this->uri); + } else { + $this->root = $this->base . $this->root_path; + } + + $this->url = $this->base . $this->uri; + + $uri = Utils::replaceFirstOccurrence(static::filterPath($this->root), '', $this->url); + + // remove the setup.php based base if set: + $setup_base = $grav['pages']->base(); + if ($setup_base) { + $uri = preg_replace('|^' . preg_quote($setup_base, '|') . '|', '', $uri); + } + $this->setup_base = $setup_base; + + // process params + $uri = $this->processParams($uri, $config->get('system.param_sep')); + + // set active language + $uri = $language->setActiveFromUri($uri); + + // split the URL and params (and make sure that the path isn't seen as domain) + $bits = static::parseUrl('http://domain.com' . $uri); + + //process fragment + if (isset($bits['fragment'])) { + $this->fragment = $bits['fragment']; + } + + // Get the path. If there's no path, make sure pathinfo() still returns dirname variable + $path = $bits['path'] ?? '/'; + + // remove the extension if there is one set + $parts = Utils::pathinfo($path); + + // set the original basename + $this->basename = $parts['basename']; + + // set the extension + if (isset($parts['extension'])) { + $this->extension = $parts['extension']; + } + + // Strip the file extension for valid page types + if ($this->isValidExtension($this->extension)) { + $path = Utils::replaceLastOccurrence(".{$this->extension}", '', $path); + } + + // set the new url + $this->url = $this->root . $path; + $this->path = static::cleanPath($path); + $this->content_path = trim(Utils::replaceFirstOccurrence($this->base, '', $this->path), '/'); + if ($this->content_path !== '') { + $this->paths = explode('/', $this->content_path); + } + + // Set some Grav stuff + $grav['base_url_absolute'] = $config->get('system.custom_base_url') ?: $this->rootUrl(true); + $grav['base_url_relative'] = $this->rootUrl(false); + $grav['base_url'] = $config->get('system.absolute_urls') ? $grav['base_url_absolute'] : $grav['base_url_relative']; + + RouteFactory::setRoot($this->root_path . $setup_base); + RouteFactory::setLanguage($language->getLanguageURLPrefix()); + RouteFactory::setParamValueDelimiter($config->get('system.param_sep')); + } + + /** + * Return URI path. + * + * @param int|null $id + * @return string|string[] + */ + public function paths($id = null) + { + if ($id !== null) { + return $this->paths[$id]; + } + + return $this->paths; + } + + + /** + * Return route to the current URI. By default route doesn't include base path. + * + * @param bool $absolute True to include full path. + * @param bool $domain True to include domain. Works only if first parameter is also true. + * @return string + */ + public function route($absolute = false, $domain = false) + { + return ($absolute ? $this->rootUrl($domain) : '') . '/' . implode('/', $this->paths); + } + + /** + * Return full query string or a single query attribute. + * + * @param string|null $id Optional attribute. Get a single query attribute if set + * @param bool $raw If true and $id is not set, return the full query array. Otherwise return the query string + * + * @return string|array Returns an array if $id = null and $raw = true + */ + public function query($id = null, $raw = false) + { + if ($id !== null) { + return $this->queries[$id] ?? null; + } + + if ($raw) { + return $this->queries; + } + + if (!$this->queries) { + return ''; + } + + return http_build_query($this->queries); + } + + /** + * Return all or a single query parameter as a URI compatible string. + * + * @param string|null $id Optional parameter name. + * @param boolean $array return the array format or not + * @return null|string|array + */ + public function params($id = null, $array = false) + { + $config = Grav::instance()['config']; + $sep = $config->get('system.param_sep'); + + $params = null; + if ($id === null) { + if ($array) { + return $this->params; + } + $output = []; + foreach ($this->params as $key => $value) { + $output[] = "{$key}{$sep}{$value}"; + $params = '/' . implode('/', $output); + } + } elseif (isset($this->params[$id])) { + if ($array) { + return $this->params[$id]; + } + $params = "/{$id}{$sep}{$this->params[$id]}"; + } + + return $params; + } + + /** + * Get URI parameter. + * + * @param string $id + * @param string|false|null $default + * @return string|false|null + */ + public function param($id, $default = false) + { + if (isset($this->params[$id])) { + return html_entity_decode(rawurldecode($this->params[$id]), ENT_COMPAT | ENT_HTML401, 'UTF-8'); + } + + return $default; + } + + /** + * Gets the Fragment portion of a URI (eg #target) + * + * @param string|null $fragment + * @return string|null + */ + public function fragment($fragment = null) + { + if ($fragment !== null) { + $this->fragment = $fragment; + } + return $this->fragment; + } + + /** + * Return URL. + * + * @param bool $include_host Include hostname. + * @return string + */ + public function url($include_host = false) + { + if ($include_host) { + return $this->url; + } + + $url = Utils::replaceFirstOccurrence($this->base, '', rtrim($this->url, '/')); + + return $url ?: '/'; + } + + /** + * Return the Path + * + * @return string The path of the URI + */ + public function path() + { + return $this->path; + } + + /** + * Return the Extension of the URI + * + * @param string|null $default + * @return string|null The extension of the URI + */ + public function extension($default = null) + { + if (!$this->extension) { + $this->extension = $default; + } + + return $this->extension; + } + + /** + * @return string + */ + public function method() + { + $method = isset($_SERVER['REQUEST_METHOD']) ? strtoupper($_SERVER['REQUEST_METHOD']) : 'GET'; + + if ($method === 'POST' && isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { + $method = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']); + } + + return $method; + } + + /** + * Return the scheme of the URI + * + * @param bool|null $raw + * @return string The scheme of the URI + */ + public function scheme($raw = false) + { + if (!$raw) { + $scheme = ''; + if ($this->scheme) { + $scheme = $this->scheme . '://'; + } elseif ($this->host) { + $scheme = '//'; + } + + return $scheme; + } + + return $this->scheme; + } + + + /** + * Return the host of the URI + * + * @return string|null The host of the URI + */ + public function host() + { + return $this->host; + } + + /** + * Return the port number if it can be figured out + * + * @param bool $raw + * @return int|null + */ + public function port($raw = false) + { + $port = $this->port; + // If not in raw mode and port is not set or is 0, figure it out from scheme. + if (!$raw && !$port) { + if ($this->scheme === 'http') { + $this->port = 80; + } elseif ($this->scheme === 'https') { + $this->port = 443; + } + } + + return $this->port ?: null; + } + + /** + * Return user + * + * @return string|null + */ + public function user() + { + return $this->user; + } + + /** + * Return password + * + * @return string|null + */ + public function password() + { + return $this->password; + } + + /** + * Gets the environment name + * + * @return string + */ + public function environment() + { + return $this->env; + } + + + /** + * Return the basename of the URI + * + * @return string The basename of the URI + */ + public function basename() + { + return $this->basename; + } + + /** + * Return the full uri + * + * @param bool $include_root + * @return string + */ + public function uri($include_root = true) + { + if ($include_root) { + return $this->uri; + } + + return Utils::replaceFirstOccurrence($this->root_path, '', $this->uri); + } + + /** + * Return the base of the URI + * + * @return string The base of the URI + */ + public function base() + { + return $this->base; + } + + /** + * Return the base relative URL including the language prefix + * or the base relative url if multi-language is not enabled + * + * @return string The base of the URI + */ + public function baseIncludingLanguage() + { + $grav = Grav::instance(); + + /** @var Pages $pages */ + $pages = $grav['pages']; + + return $pages->baseUrl(null, false); + } + + /** + * Return root URL to the site. + * + * @param bool $include_host Include hostname. + * @return string + */ + public function rootUrl($include_host = false) + { + if ($include_host) { + return $this->root; + } + + return Utils::replaceFirstOccurrence($this->base, '', $this->root); + } + + /** + * Return current page number. + * + * @return int + */ + public function currentPage() + { + $page = (int)($this->params['page'] ?? 1); + + return max(1, $page); + } + + /** + * Return relative path to the referrer defaulting to current or given page. + * + * You should set the third parameter to `true` for redirects as long as you came from the same sub-site and language. + * + * @param string|null $default + * @param string|null $attributes + * @param bool $withoutBaseRoute + * @return string + */ + public function referrer($default = null, $attributes = null, bool $withoutBaseRoute = false) + { + $referrer = $_SERVER['HTTP_REFERER'] ?? null; + + // Check that referrer came from our site. + if ($withoutBaseRoute) { + /** @var Pages $pages */ + $pages = Grav::instance()['pages']; + $base = $pages->baseUrl(null, true); + } else { + $base = $this->rootUrl(true); + } + + // Referrer should always have host set and it should come from the same base address. + if (!is_string($referrer) || !str_starts_with($referrer, $base)) { + $referrer = $default ?: $this->route(true, true); + } + + // Relative path from grav root. + $referrer = substr($referrer, strlen($base)); + if ($attributes) { + $referrer .= $attributes; + } + + return $referrer; + } + + /** + * @return string + */ + #[\ReturnTypeWillChange] + public function __toString() + { + return static::buildUrl($this->toArray()); + } + + /** + * @return string + */ + public function toOriginalString() + { + return static::buildUrl($this->toArray(true)); + } + + /** + * @param bool $full + * @return array + */ + public function toArray($full = false) + { + if ($full === true) { + $root_path = $this->root_path ?? ''; + $extension = isset($this->extension) && $this->isValidExtension($this->extension) ? '.' . $this->extension : ''; + $path = $root_path . $this->path . $extension; + } else { + $path = $this->path; + } + + return [ + 'scheme' => $this->scheme, + 'host' => $this->host, + 'port' => $this->port ?: null, + 'user' => $this->user, + 'pass' => $this->password, + 'path' => $path, + 'params' => $this->params, + 'query' => $this->query, + 'fragment' => $this->fragment + ]; + } + + /** + * Calculate the parameter regex based on the param_sep setting + * + * @return string + */ + public static function paramsRegex() + { + return '/\/{1,}([^\:\#\/\?]*' . Grav::instance()['config']->get('system.param_sep') . '[^\:\#\/\?]*)/'; + } + + /** + * Return the IP address of the current user + * + * @return string ip address + */ + public static function ip() + { + $ip = 'UNKNOWN'; + + if (getenv('HTTP_CLIENT_IP')) { + $ip = getenv('HTTP_CLIENT_IP'); + } elseif (getenv('HTTP_CF_CONNECTING_IP')) { + $ip = getenv('HTTP_CF_CONNECTING_IP'); + } elseif (getenv('HTTP_X_FORWARDED_FOR') && Grav::instance()['config']->get('system.http_x_forwarded.ip')) { + $ips = array_map('trim', explode(',', getenv('HTTP_X_FORWARDED_FOR'))); + $ip = array_shift($ips); + } elseif (getenv('HTTP_X_FORWARDED') && Grav::instance()['config']->get('system.http_x_forwarded.ip')) { + $ip = getenv('HTTP_X_FORWARDED'); + } elseif (getenv('HTTP_FORWARDED_FOR')) { + $ip = getenv('HTTP_FORWARDED_FOR'); + } elseif (getenv('HTTP_FORWARDED')) { + $ip = getenv('HTTP_FORWARDED'); + } elseif (getenv('REMOTE_ADDR')) { + $ip = getenv('REMOTE_ADDR'); + } + + return $ip; + } + + /** + * Returns current Uri. + * + * @return \Grav\Framework\Uri\Uri + */ + public static function getCurrentUri() + { + if (!static::$currentUri) { + static::$currentUri = UriFactory::createFromEnvironment($_SERVER); + } + + return static::$currentUri; + } + + /** + * Returns current route. + * + * @return Route + */ + public static function getCurrentRoute() + { + if (!static::$currentRoute) { + /** @var Uri $uri */ + $uri = Grav::instance()['uri']; + + static::$currentRoute = RouteFactory::createFromLegacyUri($uri); + } + + return static::$currentRoute; + } + + /** + * Is this an external URL? if it starts with `http` then yes, else false + * + * @param string $url the URL in question + * @return bool is eternal state + */ + public static function isExternal($url) + { + return (0 === strpos($url, 'http://') || 0 === strpos($url, 'https://') || 0 === strpos($url, '//') || 0 === strpos($url, 'mailto:') || 0 === strpos($url, 'tel:') || 0 === strpos($url, 'ftp://') || 0 === strpos($url, 'ftps://') || 0 === strpos($url, 'news:') || 0 === strpos($url, 'irc:') || 0 === strpos($url, 'gopher:') || 0 === strpos($url, 'nntp:') || 0 === strpos($url, 'feed:') || 0 === strpos($url, 'cvs:') || 0 === strpos($url, 'ssh:') || 0 === strpos($url, 'git:') || 0 === strpos($url, 'svn:') || 0 === strpos($url, 'hg:')); + } + + /** + * The opposite of built-in PHP method parse_url() + * + * @param array $parsed_url + * @return string + */ + public static function buildUrl($parsed_url) + { + $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . ':' : ''; + $authority = isset($parsed_url['host']) ? '//' : ''; + $host = $parsed_url['host'] ?? ''; + $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; + $user = $parsed_url['user'] ?? ''; + $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : ''; + $pass = ($user || $pass) ? "{$pass}@" : ''; + $path = $parsed_url['path'] ?? ''; + $path = !empty($parsed_url['params']) ? rtrim($path, '/') . static::buildParams($parsed_url['params']) : $path; + $query = !empty($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; + $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; + + return "{$scheme}{$authority}{$user}{$pass}{$host}{$port}{$path}{$query}{$fragment}"; + } + + /** + * @param array $params + * @return string + */ + public static function buildParams(array $params) + { + if (!$params) { + return ''; + } + + $grav = Grav::instance(); + $sep = $grav['config']->get('system.param_sep'); + + $output = []; + foreach ($params as $key => $value) { + $output[] = "{$key}{$sep}{$value}"; + } + + return '/' . implode('/', $output); + } + + /** + * Converts links from absolute '/' or relative (../..) to a Grav friendly format + * + * @param PageInterface $page the current page to use as reference + * @param string|array $url the URL as it was written in the markdown + * @param string $type the type of URL, image | link + * @param bool $absolute if null, will use system default, if true will use absolute links internally + * @param bool $route_only only return the route, not full URL path + * @return string|array the more friendly formatted url + */ + public static function convertUrl(PageInterface $page, $url, $type = 'link', $absolute = false, $route_only = false) + { + $grav = Grav::instance(); + + $uri = $grav['uri']; + + // Link processing should prepend language + $language = $grav['language']; + $language_append = ''; + if ($type === 'link' && $language->enabled()) { + $language_append = $language->getLanguageURLPrefix(); + } + + // Handle Excerpt style $url array + $url_path = is_array($url) ? $url['path'] : $url; + + $external = false; + $base = $grav['base_url_relative']; + $base_url = rtrim($base . $grav['pages']->base(), '/') . $language_append; + $pages_dir = $grav['locator']->findResource('page://'); + + // if absolute and starts with a base_url move on + if (isset($url['scheme']) && Utils::startsWith($url['scheme'], 'http')) { + $external = true; + } elseif ($url_path === '' && isset($url['fragment'])) { + $external = true; + } elseif ($url_path === '/' || ($base_url !== '' && Utils::startsWith($url_path, $base_url))) { + $url_path = $base_url . $url_path; + } else { + // see if page is relative to this or absolute + if (Utils::startsWith($url_path, '/')) { + $normalized_url = Utils::normalizePath($base_url . $url_path); + $normalized_path = Utils::normalizePath($pages_dir . $url_path); + } else { + $page_route = ($page->home() && !empty($url_path)) ? $page->rawRoute() : $page->route(); + $normalized_url = $base_url . Utils::normalizePath(rtrim($page_route, '/') . '/' . $url_path); + $normalized_path = Utils::normalizePath($page->path() . '/' . $url_path); + } + + // special check to see if path checking is required. + $just_path = Utils::replaceFirstOccurrence($normalized_url, '', $normalized_path); + if ($normalized_url === '/' || $just_path === $page->path()) { + $url_path = $normalized_url; + } else { + $url_bits = static::parseUrl($normalized_path); + $full_path = $url_bits['path']; + $raw_full_path = rawurldecode($full_path); + + if (file_exists($raw_full_path)) { + $full_path = $raw_full_path; + } elseif (!file_exists($full_path)) { + $full_path = false; + } + + if ($full_path) { + $path_info = Utils::pathinfo($full_path); + $page_path = $path_info['dirname']; + $filename = ''; + + if ($url_path === '..') { + $page_path = $full_path; + } else { + // save the filename if a file is part of the path + if (is_file($full_path)) { + if ($path_info['extension'] !== 'md') { + $filename = '/' . $path_info['basename']; + } + } else { + $page_path = $full_path; + } + } + + // get page instances and try to find one that fits + $instances = $grav['pages']->instances(); + if (isset($instances[$page_path])) { + /** @var PageInterface $target */ + $target = $instances[$page_path]; + $url_bits['path'] = $base_url . rtrim($target->route(), '/') . $filename; + + $url_path = Uri::buildUrl($url_bits); + } else { + $url_path = $normalized_url; + } + } else { + $url_path = $normalized_url; + } + } + } + + // handle absolute URLs + if (is_array($url) && !$external && ($absolute === true || $grav['config']->get('system.absolute_urls', false))) { + $url['scheme'] = $uri->scheme(true); + $url['host'] = $uri->host(); + $url['port'] = $uri->port(true); + + // check if page exists for this route, and if so, check if it has SSL enabled + $pages = $grav['pages']; + $routes = $pages->routes(); + + // if this is an image, get the proper path + $url_bits = Utils::pathinfo($url_path); + if (isset($url_bits['extension'])) { + $target_path = $url_bits['dirname']; + } else { + $target_path = $url_path; + } + + // strip base from this path + $target_path = Utils::replaceFirstOccurrence($uri->rootUrl(), '', $target_path); + + // set to / if root + if (empty($target_path)) { + $target_path = '/'; + } + + // look to see if this page exists and has ssl enabled + if (isset($routes[$target_path])) { + $target_page = $pages->get($routes[$target_path]); + if ($target_page) { + $ssl_enabled = $target_page->ssl(); + if ($ssl_enabled !== null) { + if ($ssl_enabled) { + $url['scheme'] = 'https'; + } else { + $url['scheme'] = 'http'; + } + } + } + } + } + + // Handle route only + if ($route_only) { + $url_path = Utils::replaceFirstOccurrence(static::filterPath($base_url), '', $url_path); + } + + // transform back to string/array as needed + if (is_array($url)) { + $url['path'] = $url_path; + } else { + $url = $url_path; + } + + return $url; + } + + /** + * @param string $url + * @return array|false + */ + public static function parseUrl($url) + { + $grav = Grav::instance(); + + // Remove extra slash from streams, parse_url() doesn't like it. + $url = preg_replace('/([^:])(\/{2,})/', '$1/', $url); + + $encodedUrl = preg_replace_callback( + '%[^:/@?&=#]+%usD', + static function ($matches) { + return rawurlencode($matches[0]); + }, + $url + ); + + $parts = parse_url($encodedUrl); + + if (false === $parts) { + return false; + } + + foreach ($parts as $name => $value) { + $parts[$name] = rawurldecode($value); + } + + if (!isset($parts['path'])) { + $parts['path'] = ''; + } + + [$stripped_path, $params] = static::extractParams($parts['path'], $grav['config']->get('system.param_sep')); + + if (!empty($params)) { + $parts['path'] = $stripped_path; + $parts['params'] = $params; + } + + return $parts; + } + + /** + * @param string $uri + * @param string $delimiter + * @return array + */ + public static function extractParams($uri, $delimiter) + { + $params = []; + + if (strpos($uri, $delimiter) !== false) { + preg_match_all(static::paramsRegex(), $uri, $matches, PREG_SET_ORDER); + + foreach ($matches as $match) { + $param = explode($delimiter, $match[1]); + if (count($param) === 2) { + $plain_var = htmlspecialchars(strip_tags(rawurldecode($param[1])), ENT_QUOTES, 'UTF-8'); + $params[$param[0]] = $plain_var; + $uri = str_replace($match[0], '', $uri); + } + } + } + + return [$uri, $params]; + } + + /** + * Converts links from absolute '/' or relative (../..) to a Grav friendly format + * + * @param PageInterface $page the current page to use as reference + * @param string $markdown_url the URL as it was written in the markdown + * @param string $type the type of URL, image | link + * @param bool|null $relative if null, will use system default, if true will use relative links internally + * + * @return string the more friendly formatted url + */ + public static function convertUrlOld(PageInterface $page, $markdown_url, $type = 'link', $relative = null) + { + $grav = Grav::instance(); + + $language = $grav['language']; + + // Link processing should prepend language + $language_append = ''; + if ($type === 'link' && $language->enabled()) { + $language_append = $language->getLanguageURLPrefix(); + } + $pages_dir = $grav['locator']->findResource('page://'); + if ($relative === null) { + $base = $grav['base_url']; + } else { + $base = $relative ? $grav['base_url_relative'] : $grav['base_url_absolute']; + } + + $base_url = rtrim($base . $grav['pages']->base(), '/') . $language_append; + + // if absolute and starts with a base_url move on + if (Utils::pathinfo($markdown_url, PATHINFO_DIRNAME) === '.' && $page->url() === '/') { + return '/' . $markdown_url; + } + // no path to convert + if ($base_url !== '' && Utils::startsWith($markdown_url, $base_url)) { + return $markdown_url; + } + // if contains only a fragment + if (Utils::startsWith($markdown_url, '#')) { + return $markdown_url; + } + + $target = null; + // see if page is relative to this or absolute + if (Utils::startsWith($markdown_url, '/')) { + $normalized_url = Utils::normalizePath($base_url . $markdown_url); + $normalized_path = Utils::normalizePath($pages_dir . $markdown_url); + } else { + $normalized_url = $base_url . Utils::normalizePath($page->route() . '/' . $markdown_url); + $normalized_path = Utils::normalizePath($page->path() . '/' . $markdown_url); + } + + // special check to see if path checking is required. + $just_path = Utils::replaceFirstOccurrence($normalized_url, '', $normalized_path); + if ($just_path === $page->path()) { + return $normalized_url; + } + + $url_bits = parse_url($normalized_path); + $full_path = $url_bits['path']; + + if (file_exists($full_path)) { + // do nothing + } elseif (file_exists(rawurldecode($full_path))) { + $full_path = rawurldecode($full_path); + } else { + return $normalized_url; + } + + $path_info = Utils::pathinfo($full_path); + $page_path = $path_info['dirname']; + $filename = ''; + + if ($markdown_url === '..') { + $page_path = $full_path; + } else { + // save the filename if a file is part of the path + if (is_file($full_path)) { + if ($path_info['extension'] !== 'md') { + $filename = '/' . $path_info['basename']; + } + } else { + $page_path = $full_path; + } + } + + // get page instances and try to find one that fits + $instances = $grav['pages']->instances(); + if (isset($instances[$page_path])) { + /** @var PageInterface $target */ + $target = $instances[$page_path]; + $url_bits['path'] = $base_url . rtrim($target->route(), '/') . $filename; + + return static::buildUrl($url_bits); + } + + return $normalized_url; + } + + /** + * Adds the nonce to a URL for a specific action + * + * @param string $url the url + * @param string $action the action + * @param string $nonceParamName the param name to use + * + * @return string the url with the nonce + */ + public static function addNonce($url, $action, $nonceParamName = 'nonce') + { + $fake = $url && strpos($url, '/') === 0; + + if ($fake) { + $url = 'http://domain.com' . $url; + } + $uri = new static($url); + $parts = $uri->toArray(); + $nonce = Utils::getNonce($action); + $parts['params'] = ($parts['params'] ?? []) + [$nonceParamName => $nonce]; + + if ($fake) { + unset($parts['scheme'], $parts['host']); + } + + return static::buildUrl($parts); + } + + /** + * Is the passed in URL a valid URL? + * + * @param string $url + * @return bool + */ + public static function isValidUrl($url) + { + $regex = '/^(?:(https?|ftp|telnet):)?\/\/((?:[a-z0-9@:.-]|%[0-9A-F]{2}){3,})(?::(\d+))?((?:\/(?:[a-z0-9-._~!$&\'\(\)\*\+\,\;\=\:\@]|%[0-9A-F]{2})*)*)(?:\?((?:[a-z0-9-._~!$&\'\(\)\*\+\,\;\=\:\/?@]|%[0-9A-F]{2})*))?(?:#((?:[a-z0-9-._~!$&\'\(\)\*\+\,\;\=\:\/?@]|%[0-9A-F]{2})*))?/'; + + return (bool)preg_match($regex, $url); + } + + /** + * Removes extra double slashes and fixes back-slashes + * + * @param string $path + * @return string + */ + public static function cleanPath($path) + { + $regex = '/(\/)\/+/'; + $path = str_replace(['\\', '/ /'], '/', $path); + $path = preg_replace($regex, '$1', $path); + + return $path; + } + + /** + * Filters the user info string. + * + * @param string|null $info The raw user or password. + * @return string The percent-encoded user or password string. + */ + public static function filterUserInfo($info) + { + return $info !== null ? UriPartsFilter::filterUserInfo($info) : ''; + } + + /** + * Filter Uri path. + * + * This method percent-encodes all reserved + * characters in the provided path string. This method + * will NOT double-encode characters that are already + * percent-encoded. + * + * @param string|null $path The raw uri path. + * @return string The RFC 3986 percent-encoded uri path. + * @link http://www.faqs.org/rfcs/rfc3986.html + */ + public static function filterPath($path) + { + return $path !== null ? UriPartsFilter::filterPath($path) : ''; + } + + /** + * Filters the query string or fragment of a URI. + * + * @param string|null $query The raw uri query string. + * @return string The percent-encoded query string. + */ + public static function filterQuery($query) + { + return $query !== null ? UriPartsFilter::filterQueryOrFragment($query) : ''; + } + + /** + * @param array $env + * @return void + */ + protected function createFromEnvironment(array $env) + { + // Build scheme. + if (isset($env['HTTP_X_FORWARDED_PROTO']) && Grav::instance()['config']->get('system.http_x_forwarded.protocol')) { + $this->scheme = $env['HTTP_X_FORWARDED_PROTO']; + } elseif (isset($env['X-FORWARDED-PROTO'])) { + $this->scheme = $env['X-FORWARDED-PROTO']; + } elseif (isset($env['HTTP_CLOUDFRONT_FORWARDED_PROTO'])) { + $this->scheme = $env['HTTP_CLOUDFRONT_FORWARDED_PROTO']; + } elseif (isset($env['REQUEST_SCHEME']) && empty($env['HTTPS'])) { + $this->scheme = $env['REQUEST_SCHEME']; + } else { + $https = $env['HTTPS'] ?? ''; + $this->scheme = (empty($https) || strtolower($https) === 'off') ? 'http' : 'https'; + } + + // Build user and password. + $this->user = $env['PHP_AUTH_USER'] ?? null; + $this->password = $env['PHP_AUTH_PW'] ?? null; + + // Build host. + if (isset($env['HTTP_X_FORWARDED_HOST']) && Grav::instance()['config']->get('system.http_x_forwarded.host')) { + $hostname = $env['HTTP_X_FORWARDED_HOST']; + } else if (isset($env['HTTP_HOST'])) { + $hostname = $env['HTTP_HOST']; + } elseif (isset($env['SERVER_NAME'])) { + $hostname = $env['SERVER_NAME']; + } else { + $hostname = 'localhost'; + } + // Remove port from HTTP_HOST generated $hostname + $hostname = Utils::substrToString($hostname, ':'); + // Validate the hostname + $this->host = $this->validateHostname($hostname) ? $hostname : 'unknown'; + + // Build port. + if (isset($env['HTTP_X_FORWARDED_PORT']) && Grav::instance()['config']->get('system.http_x_forwarded.port')) { + $this->port = (int)$env['HTTP_X_FORWARDED_PORT']; + } elseif (isset($env['X-FORWARDED-PORT'])) { + $this->port = (int)$env['X-FORWARDED-PORT']; + } elseif (isset($env['HTTP_CLOUDFRONT_FORWARDED_PROTO'])) { + // Since AWS Cloudfront does not provide a forwarded port header, + // we have to build the port using the scheme. + $this->port = $this->port(); + } elseif (isset($env['SERVER_PORT'])) { + $this->port = (int)$env['SERVER_PORT']; + } else { + $this->port = null; + } + + if ($this->port === 0 || $this->hasStandardPort()) { + $this->port = null; + } + + // Build path. + $request_uri = $env['REQUEST_URI'] ?? '/'; + $this->path = rawurldecode(parse_url('http://example.com' . $request_uri, PHP_URL_PATH)); + + // Build query string. + $this->query = $env['QUERY_STRING'] ?? ''; + if ($this->query === '') { + $this->query = parse_url('http://example.com' . $request_uri, PHP_URL_QUERY) ?? ''; + } + + // Support ngnix routes. + if (strpos($this->query, '_url=') === 0) { + parse_str($this->query, $query); + unset($query['_url']); + $this->query = http_build_query($query); + } + + // Build fragment. + $this->fragment = null; + + // Filter userinfo, path and query string. + $this->user = $this->user !== null ? static::filterUserInfo($this->user) : null; + $this->password = $this->password !== null ? static::filterUserInfo($this->password) : null; + $this->path = empty($this->path) ? '/' : static::filterPath($this->path); + $this->query = static::filterQuery($this->query); + + $this->reset(); + } + + /** + * Does this Uri use a standard port? + * + * @return bool + */ + protected function hasStandardPort() + { + return (!$this->port || $this->port === 80 || $this->port === 443); + } + + /** + * @param string $url + */ + protected function createFromString($url) + { + // Set Uri parts. + $parts = parse_url($url); + if ($parts === false) { + throw new RuntimeException('Malformed URL: ' . $url); + } + $port = (int)($parts['port'] ?? 0); + + $this->scheme = $parts['scheme'] ?? null; + $this->user = $parts['user'] ?? null; + $this->password = $parts['pass'] ?? null; + $this->host = $parts['host'] ?? null; + $this->port = $port ?: null; + $this->path = $parts['path'] ?? ''; + $this->query = $parts['query'] ?? ''; + $this->fragment = $parts['fragment'] ?? null; + + // Validate the hostname + if ($this->host) { + $this->host = $this->validateHostname($this->host) ? $this->host : 'unknown'; + } + // Filter userinfo, path, query string and fragment. + $this->user = $this->user !== null ? static::filterUserInfo($this->user) : null; + $this->password = $this->password !== null ? static::filterUserInfo($this->password) : null; + $this->path = empty($this->path) ? '/' : static::filterPath($this->path); + $this->query = static::filterQuery($this->query); + $this->fragment = $this->fragment !== null ? static::filterQuery($this->fragment) : null; + + $this->reset(); + } + + /** + * @return void + */ + protected function reset() + { + // resets + parse_str($this->query, $this->queries); + $this->extension = null; + $this->basename = null; + $this->paths = []; + $this->params = []; + $this->env = $this->buildEnvironment(); + $this->uri = $this->path . (!empty($this->query) ? '?' . $this->query : ''); + + $this->base = $this->buildBaseUrl(); + $this->root_path = $this->buildRootPath(); + $this->root = $this->base . $this->root_path; + $this->url = $this->base . $this->uri; + } + + /** + * Get post from either $_POST or JSON response object + * By default returns all data, or can return a single item + * + * @param string|null $element + * @param string|null $filter_type + * @return array|null + */ + public function post($element = null, $filter_type = null) + { + if (!$this->post) { + $content_type = $this->getContentType(); + if ($content_type === 'application/json') { + $json = file_get_contents('php://input'); + $this->post = json_decode($json, true); + } elseif (!empty($_POST)) { + $this->post = (array)$_POST; + } + + $event = new Event(['post' => &$this->post]); + Grav::instance()->fireEvent('onHttpPostFilter', $event); + } + + if ($this->post && null !== $element) { + $item = Utils::getDotNotation($this->post, $element); + if ($filter_type) { + if ($filter_type === FILTER_SANITIZE_STRING || $filter_type === GRAV_SANITIZE_STRING) { + $item = htmlspecialchars(strip_tags($item), ENT_QUOTES, 'UTF-8'); + } else { + $item = filter_var($item, $filter_type); + } + } + return $item; + } + + return $this->post; + } + + /** + * Get content type from request + * + * @param bool $short + * @return null|string + */ + public function getContentType($short = true) + { + $content_type = $_SERVER['CONTENT_TYPE'] ?? $_SERVER['HTTP_CONTENT_TYPE'] ?? $_SERVER['HTTP_ACCEPT'] ?? null; + if ($content_type) { + if ($short) { + return Utils::substrToString($content_type, ';'); + } + } + return $content_type; + } + + /** + * Check if this is a valid Grav extension + * + * @param string|null $extension + * @return bool + */ + public function isValidExtension($extension): bool + { + $extension = (string)$extension; + + return $extension !== '' && in_array($extension, Utils::getSupportPageTypes(), true); + } + + /** + * Allow overriding of any element (be careful!) + * + * @param array $data + * @return Uri + */ + public function setUriProperties($data) + { + foreach (get_object_vars($this) as $property => $default) { + if (!array_key_exists($property, $data)) { + continue; + } + $this->{$property} = $data[$property]; // assign value to object + } + return $this; + } + + + /** + * Compatibility in case getallheaders() is not available on platform + */ + public static function getAllHeaders() + { + if (!function_exists('getallheaders')) { + $headers = []; + foreach ($_SERVER as $name => $value) { + if (substr($name, 0, 5) == 'HTTP_') { + $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; + } + } + return $headers; + } + return getallheaders(); + } + + /** + * Get the base URI with port if needed + * + * @return string + */ + private function buildBaseUrl() + { + return $this->scheme() . $this->host; + } + + /** + * Get the Grav Root Path + * + * @return string + */ + private function buildRootPath() + { + // In Windows script path uses backslash, convert it: + $scriptPath = str_replace('\\', '/', $_SERVER['PHP_SELF']); + $rootPath = str_replace(' ', '%20', rtrim(substr($scriptPath, 0, strpos($scriptPath, 'index.php')), '/')); + + return $rootPath; + } + + /** + * @return string + */ + private function buildEnvironment() + { + // check for localhost variations + if ($this->host === '127.0.0.1' || $this->host === '::1') { + return 'localhost'; + } + + return $this->host ?: 'unknown'; + } + + /** + * Process any params based in this URL, supports any valid delimiter + * + * @param string $uri + * @param string $delimiter + * @return string + */ + private function processParams(string $uri, string $delimiter = ':'): string + { + if (strpos($uri, $delimiter) !== false) { + preg_match_all(static::paramsRegex(), $uri, $matches, PREG_SET_ORDER); + + foreach ($matches as $match) { + $param = explode($delimiter, $match[1]); + if (count($param) === 2) { + $plain_var = htmlspecialchars(strip_tags($param[1]), ENT_QUOTES, 'UTF-8'); + $this->params[$param[0]] = $plain_var; + $uri = str_replace($match[0], '', $uri); + } + } + } + return $uri; + } +} diff --git a/system/src/Grav/Common/User/Access.php b/system/src/Grav/Common/User/Access.php new file mode 100644 index 0000000..c93e1be --- /dev/null +++ b/system/src/Grav/Common/User/Access.php @@ -0,0 +1,52 @@ + ['admin.configuration_system'], + 'admin.configuration.site' => ['admin.configuration_site', 'admin.settings'], + 'admin.configuration.media' => ['admin.configuration_media'], + 'admin.configuration.info' => ['admin.configuration_info'], + ]; + + /** + * @param string $action + * @return bool|null + */ + public function get(string $action) + { + $result = parent::get($action); + if (is_bool($result)) { + return $result; + } + + // Get access value. + if (isset($this->aliases[$action])) { + $aliases = $this->aliases[$action]; + foreach ($aliases as $alias) { + $result = parent::get($alias); + if (is_bool($result)) { + return $result; + } + } + } + + return null; + } +} diff --git a/system/src/Grav/Common/User/Authentication.php b/system/src/Grav/Common/User/Authentication.php new file mode 100644 index 0000000..71d4158 --- /dev/null +++ b/system/src/Grav/Common/User/Authentication.php @@ -0,0 +1,61 @@ +get('user/account'); + } + + parent::__construct($items, $blueprints); + } + + /** + * @param string $offset + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($offset) + { + $value = parent::offsetExists($offset); + + // Handle special case where user was logged in before 'authorized' was added to the user object. + if (false === $value && $offset === 'authorized') { + $value = $this->offsetExists('authenticated'); + } + + return $value; + } + + /** + * @param string $offset + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + $value = parent::offsetGet($offset); + + // Handle special case where user was logged in before 'authorized' was added to the user object. + if (null === $value && $offset === 'authorized') { + $value = $this->offsetGet('authenticated'); + $this->offsetSet($offset, $value); + } + + return $value; + } + + /** + * @return bool + */ + public function isValid(): bool + { + return $this->items !== null; + } + + /** + * Update object with data + * + * @param array $data + * @param array $files + * @return $this + */ + public function update(array $data, array $files = []) + { + // Note: $this->merge() would cause infinite loop as it calls this method. + parent::merge($data); + + return $this; + } + + /** + * Save user + * + * @return void + */ + public function save() + { + /** @var CompiledYamlFile|null $file */ + $file = $this->file(); + if (!$file || !$file->filename()) { + user_error(__CLASS__ . ': calling \$user = new ' . __CLASS__ . "() is deprecated since Grav 1.6, use \$grav['accounts']->load(\$username) or \$grav['accounts']->load('') instead", E_USER_DEPRECATED); + } + + if ($file) { + $username = $this->filterUsername((string)$this->get('username')); + + if (!$file->filename()) { + $locator = Grav::instance()['locator']; + $file->filename($locator->findResource('account://' . $username . YAML_EXT, true, true)); + } + + // if plain text password, hash it and remove plain text + $password = $this->get('password') ?? $this->get('password1'); + if (null !== $password && '' !== $password) { + $password2 = $this->get('password2'); + if (!\is_string($password) || ($password2 && $password !== $password2)) { + throw new \RuntimeException('Passwords did not match.'); + } + + $this->set('hashed_password', Authentication::create($password)); + } + $this->undef('password'); + $this->undef('password1'); + $this->undef('password2'); + + $data = $this->items; + if ($username === $data['username']) { + unset($data['username']); + } + unset($data['authenticated'], $data['authorized']); + + $file->save($data); + + // We need to signal Flex Users about the change. + /** @var Flex|null $flex */ + $flex = Grav::instance()['flex'] ?? null; + $users = $flex ? $flex->getDirectory('user-accounts') : null; + if (null !== $users) { + $users->clearCache(); + } + } + } + + /** + * @return MediaCollectionInterface|Media + */ + public function getMedia() + { + if (null === $this->_media) { + // Media object should only contain avatar, nothing else. + $media = new Media($this->getMediaFolder() ?? '', $this->getMediaOrder(), false); + + $path = $this->getAvatarFile(); + if ($path && is_file($path)) { + $medium = MediumFactory::fromFile($path); + if ($medium) { + $media->add(Utils::basename($path), $medium); + } + } + + $this->_media = $media; + } + + return $this->_media; + } + + /** + * @return string + */ + public function getMediaFolder() + { + return $this->blueprints()->fields()['avatar']['destination'] ?? 'account://avatars'; + } + + /** + * @return array + */ + public function getMediaOrder() + { + return []; + } + + /** + * Serialize user. + * + * @return string[] + */ + public function __sleep() + { + return [ + 'items', + 'storage' + ]; + } + + /** + * Unserialize user. + */ + public function __wakeup() + { + $this->gettersVariable = 'items'; + $this->nestedSeparator = '.'; + + if (null === $this->items) { + $this->items = []; + } + + // Always set blueprints. + if (null === $this->blueprints) { + $this->blueprints = (new Blueprints)->get('user/account'); + } + } + + /** + * Merge two configurations together. + * + * @param array $data + * @return $this + * @deprecated 1.6 Use `->update($data)` instead (same but with data validation & filtering, file upload support). + */ + public function merge(array $data) + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->update($data) method instead', E_USER_DEPRECATED); + + return $this->update($data); + } + + /** + * Return media object for the User's avatar. + * + * @return Medium|null + * @deprecated 1.6 Use ->getAvatarImage() method instead. + */ + public function getAvatarMedia() + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use getAvatarImage() method instead', E_USER_DEPRECATED); + + return $this->getAvatarImage(); + } + + /** + * Return the User's avatar URL + * + * @return string + * @deprecated 1.6 Use ->getAvatarUrl() method instead. + */ + public function avatarUrl() + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use getAvatarUrl() method instead', E_USER_DEPRECATED); + + return $this->getAvatarUrl(); + } + + /** + * Checks user authorization to the action. + * Ensures backwards compatibility + * + * @param string $action + * @return bool + * @deprecated 1.5 Use ->authorize() method instead. + */ + public function authorise($action) + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use authorize() method instead', E_USER_DEPRECATED); + + return $this->authorize($action) ?? false; + } + + /** + * Implements Countable interface. + * + * @return int + * @deprecated 1.6 Method makes no sense for user account. + */ + #[\ReturnTypeWillChange] + public function count() + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6', E_USER_DEPRECATED); + + return parent::count(); + } + + /** + * @param string $username + * @return string + */ + protected function filterUsername(string $username): string + { + return mb_strtolower($username); + } + + /** + * @return string|null + */ + protected function getAvatarFile(): ?string + { + $avatars = $this->get('avatar'); + if (is_array($avatars) && $avatars) { + $avatar = array_shift($avatars); + return $avatar['path'] ?? null; + } + + return null; + } +} diff --git a/system/src/Grav/Common/User/DataUser/UserCollection.php b/system/src/Grav/Common/User/DataUser/UserCollection.php new file mode 100644 index 0000000..a1def0e --- /dev/null +++ b/system/src/Grav/Common/User/DataUser/UserCollection.php @@ -0,0 +1,163 @@ +className = $className; + } + + /** + * Load user account. + * + * Always creates user object. To check if user exists, use $this->exists(). + * + * @param string $username + * @return UserInterface + */ + public function load($username): UserInterface + { + $username = (string)$username; + + $grav = Grav::instance(); + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + + // Filter username. + $username = $this->filterUsername($username); + + $filename = 'account://' . $username . YAML_EXT; + $path = $locator->findResource($filename) ?: $locator->findResource($filename, true, true); + if (!is_string($path)) { + throw new RuntimeException('Internal Error'); + } + $file = CompiledYamlFile::instance($path); + $content = (array)$file->content() + ['username' => $username, 'state' => 'enabled']; + + $userClass = $this->className; + $callable = static function () { + $blueprints = new Blueprints; + + return $blueprints->get('user/account'); + }; + + /** @var UserInterface $user */ + $user = new $userClass($content, $callable); + $user->file($file); + + return $user; + } + + /** + * Find a user by username, email, etc + * + * @param string $query the query to search for + * @param array $fields the fields to search + * @return UserInterface + */ + public function find($query, $fields = ['username', 'email']): UserInterface + { + $fields = (array)$fields; + + $grav = Grav::instance(); + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + + $account_dir = $locator->findResource('account://'); + if (!is_string($account_dir)) { + return $this->load(''); + } + + $files = array_diff(scandir($account_dir) ?: [], ['.', '..']); + + // Try with username first, you never know! + if (in_array('username', $fields, true)) { + $user = $this->load($query); + unset($fields[array_search('username', $fields, true)]); + } else { + $user = $this->load(''); + } + + // If not found, try the fields + if (!$user->exists()) { + $query = mb_strtolower($query); + foreach ($files as $file) { + if (Utils::endsWith($file, YAML_EXT)) { + $find_user = $this->load(trim(Utils::pathinfo($file, PATHINFO_FILENAME))); + foreach ($fields as $field) { + if (isset($find_user[$field]) && mb_strtolower($find_user[$field]) === $query) { + return $find_user; + } + } + } + } + } + return $user; + } + + /** + * Remove user account. + * + * @param string $username + * @return bool True if the action was performed + */ + public function delete($username): bool + { + $file_path = Grav::instance()['locator']->findResource('account://' . $username . YAML_EXT); + + return $file_path && unlink($file_path); + } + + /** + * @return int + */ + public function count(): int + { + // check for existence of a user account + $account_dir = $file_path = Grav::instance()['locator']->findResource('account://'); + $accounts = glob($account_dir . '/*.yaml') ?: []; + + return count($accounts); + } + + /** + * @param string $username + * @return string + */ + protected function filterUsername(string $username): string + { + return mb_strtolower($username); + } +} diff --git a/system/src/Grav/Common/User/Group.php b/system/src/Grav/Common/User/Group.php new file mode 100644 index 0000000..c6a14df --- /dev/null +++ b/system/src/Grav/Common/User/Group.php @@ -0,0 +1,172 @@ +get('groups', []); + } + + /** + * Get the groups list + * + * @return array + * @deprecated 1.7, use $grav['user_groups'] Flex UserGroupCollection instead + */ + public static function groupNames() + { + user_error(__METHOD__ . '() is deprecated since Grav 1.7, use $grav[\'user_groups\'] Flex UserGroupCollection instead', E_USER_DEPRECATED); + + $groups = []; + + foreach (static::groups() as $groupname => $group) { + $groups[$groupname] = $group['readableName'] ?? $groupname; + } + + return $groups; + } + + /** + * Checks if a group exists + * + * @param string $groupname + * @return bool + * @deprecated 1.7, use $grav['user_groups'] Flex UserGroupCollection instead + */ + public static function groupExists($groupname) + { + user_error(__METHOD__ . '() is deprecated since Grav 1.7, use $grav[\'user_groups\'] Flex UserGroupCollection instead', E_USER_DEPRECATED); + + return isset(self::groups()[$groupname]); + } + + /** + * Get a group by name + * + * @param string $groupname + * @return object + * @deprecated 1.7, use $grav['user_groups'] Flex UserGroupCollection instead + */ + public static function load($groupname) + { + user_error(__METHOD__ . '() is deprecated since Grav 1.7, use $grav[\'user_groups\'] Flex UserGroupCollection instead', E_USER_DEPRECATED); + + $groups = self::groups(); + + $content = $groups[$groupname] ?? []; + $content += ['groupname' => $groupname]; + + $blueprints = new Blueprints(); + $blueprint = $blueprints->get('user/group'); + + return new Group($content, $blueprint); + } + + /** + * Save a group + * + * @return void + */ + public function save() + { + $grav = Grav::instance(); + + /** @var Config $config */ + $config = $grav['config']; + + $blueprints = new Blueprints(); + $blueprint = $blueprints->get('user/group'); + + $config->set("groups.{$this->get('groupname')}", []); + + $fields = $blueprint->fields(); + foreach ($fields as $field) { + if ($field['type'] === 'text') { + $value = $field['name']; + if (isset($this->items['data'][$value])) { + $config->set("groups.{$this->get('groupname')}.{$value}", $this->items['data'][$value]); + } + } + if ($field['type'] === 'array' || $field['type'] === 'permissions') { + $value = $field['name']; + $arrayValues = Utils::getDotNotation($this->items['data'], $field['name']); + + if ($arrayValues) { + foreach ($arrayValues as $arrayIndex => $arrayValue) { + $config->set("groups.{$this->get('groupname')}.{$value}.{$arrayIndex}", $arrayValue); + } + } + } + } + + $type = 'groups'; + $blueprints = $this->blueprints(); + + $filename = CompiledYamlFile::instance($grav['locator']->findResource("config://{$type}.yaml")); + + $obj = new Data($config->get($type), $blueprints); + $obj->file($filename); + $obj->save(); + } + + /** + * Remove a group + * + * @param string $groupname + * @return bool True if the action was performed + * @deprecated 1.7, use $grav['user_groups'] Flex UserGroupCollection instead + */ + public static function remove($groupname) + { + user_error(__METHOD__ . '() is deprecated since Grav 1.7, use $grav[\'user_groups\'] Flex UserGroupCollection instead', E_USER_DEPRECATED); + + $grav = Grav::instance(); + + /** @var Config $config */ + $config = $grav['config']; + + $blueprints = new Blueprints(); + $blueprint = $blueprints->get('user/group'); + + $type = 'groups'; + + $groups = $config->get($type); + unset($groups[$groupname]); + $config->set($type, $groups); + + $filename = CompiledYamlFile::instance($grav['locator']->findResource("config://{$type}.yaml")); + + $obj = new Data($groups, $blueprint); + $obj->file($filename); + $obj->save(); + + return true; + } +} diff --git a/system/src/Grav/Common/User/Interfaces/AuthorizeInterface.php b/system/src/Grav/Common/User/Interfaces/AuthorizeInterface.php new file mode 100644 index 0000000..1566649 --- /dev/null +++ b/system/src/Grav/Common/User/Interfaces/AuthorizeInterface.php @@ -0,0 +1,26 @@ +exists(). + * + * @param string $username + * @return UserInterface + */ + public function load($username): UserInterface; + + /** + * Find a user by username, email, etc + * + * @param string $query the query to search for + * @param array $fields the fields to search + * @return UserInterface + */ + public function find($query, $fields = ['username', 'email']): UserInterface; + + /** + * Delete user account. + * + * @param string $username + * @return bool True if user account was found and was deleted. + */ + public function delete($username): bool; +} diff --git a/system/src/Grav/Common/User/Interfaces/UserGroupInterface.php b/system/src/Grav/Common/User/Interfaces/UserGroupInterface.php new file mode 100644 index 0000000..2baa91f --- /dev/null +++ b/system/src/Grav/Common/User/Interfaces/UserGroupInterface.php @@ -0,0 +1,18 @@ +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|null $separator Separator, defaults to '.' + * @return mixed Value. + */ + public function get($name, $default = null, $separator = null); + + /** + * Set value by using dot notation for nested arrays/objects. + * + * @example $data->set('this.is.my.nested.variable', $value); + * + * @param string $name Dot separated path to the requested value. + * @param mixed $value New value. + * @param string|null $separator Separator, defaults to '.' + * @return $this + */ + public function set($name, $value, $separator = null); + + /** + * Unset value by using dot notation for nested arrays/objects. + * + * @example $data->undef('this.is.my.nested.variable'); + * + * @param string $name Dot separated path to the requested value. + * @param string|null $separator Separator, defaults to '.' + * @return $this + */ + public function undef($name, $separator = null); + + /** + * 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|null $separator Separator, defaults to '.' + * @return $this + */ + public function def($name, $default = null, $separator = null); + + /** + * Join nested values together by using blueprints. + * + * @param string $name Dot separated path to the requested value. + * @param mixed $value Value to be joined. + * @param string $separator Separator, defaults to '.' + * @return $this + * @throws RuntimeException + */ + public function join($name, $value, $separator = '.'); + + /** + * Get nested structure containing default values defined in the blueprints. + * + * Fields without default value are ignored in the list. + + * @return array + */ + public function getDefaults(); + + /** + * Set default values by using blueprints. + * + * @param string $name Dot separated path to the requested value. + * @param mixed $value Value to be joined. + * @param string $separator Separator, defaults to '.' + * @return $this + */ + public function joinDefaults($name, $value, $separator = '.'); + + /** + * Get value from the configuration and join it with given data. + * + * @param string $name Dot separated path to the requested value. + * @param array|object $value Value to be joined. + * @param string $separator Separator, defaults to '.' + * @return array + * @throws RuntimeException + */ + public function getJoined($name, $value, $separator = '.'); + + /** + * Set default values to the configuration if variables were not set. + * + * @param array $data + * @return $this + */ + public function setDefaults(array $data); + + /** + * Update object with data + * + * @param array $data + * @param array $files + * @return $this + */ + public function update(array $data, array $files = []); + + /** + * 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 unmodified data as raw string. + * + * NOTE: This function only returns data which has been saved to the storage. + * + * @return string + */ + public function raw(); + + /** + * Authenticate user. + * + * If user password needs to be updated, new information will be saved. + * + * @param string $password Plaintext password. + * @return bool + */ + public function authenticate(string $password): bool; + + /** + * Return media object for the User's avatar. + * + * Note: if there's no local avatar image for the user, you should call getAvatarUrl() to get the external avatar URL. + * + * @return Medium|null + */ + public function getAvatarImage(): ?Medium; + + /** + * Return the User's avatar URL. + * + * @return string + */ + public function getAvatarUrl(): string; +} diff --git a/system/src/Grav/Common/User/Traits/UserTrait.php b/system/src/Grav/Common/User/Traits/UserTrait.php new file mode 100644 index 0000000..6126c58 --- /dev/null +++ b/system/src/Grav/Common/User/Traits/UserTrait.php @@ -0,0 +1,233 @@ +get('hashed_password'); + + $isHashed = null !== $hash; + if (!$isHashed) { + // If there is no hashed password, fake verify with default hash. + $hash = Grav::instance()['config']->get('system.security.default_hash'); + } + + // Always execute verify() to protect us from timing attacks, but make the test to fail if hashed password wasn't set. + $result = Authentication::verify($password, $hash) && $isHashed; + + $plaintext_password = $this->get('password'); + if (null !== $plaintext_password) { + // Plain-text password is still stored, check if it matches. + if ($password !== $plaintext_password) { + return false; + } + + // Force hash update to get rid of plaintext password. + $result = 2; + } + + if ($result === 2) { + // Password needs to be updated, save the user. + $this->set('password', $password); + $this->undef('hashed_password'); + $this->save(); + } + + return (bool)$result; + } + + /** + * Checks user authorization to the action. + * + * @param string $action + * @param string|null $scope + * @return bool|null + */ + public function authorize(string $action, string $scope = null): ?bool + { + // User needs to be enabled. + if ($this->get('state', 'enabled') !== 'enabled') { + return false; + } + + // User needs to be logged in. + if (!$this->get('authenticated')) { + return false; + } + + // User needs to be authorized (2FA). + if (strpos($action, 'login') === false && !$this->get('authorized', true)) { + return false; + } + + if (null !== $scope) { + $action = $scope . '.' . $action; + } + + $config = Grav::instance()['config']; + $authorized = false; + + //Check group access level + $groups = (array)$this->get('groups'); + foreach ($groups as $group) { + $permission = $config->get("groups.{$group}.access.{$action}"); + $authorized = Utils::isPositive($permission); + if ($authorized === true) { + break; + } + } + + //Check user access level + $access = $this->get('access'); + if ($access && Utils::getDotNotation($access, $action) !== null) { + $permission = $this->get("access.{$action}"); + $authorized = Utils::isPositive($permission); + } + + return $authorized; + } + + /** + * Return media object for the User's avatar. + * + * Note: if there's no local avatar image for the user, you should call getAvatarUrl() to get the external avatar URL. + * + * @return ImageMedium|StaticImageMedium|null + */ + public function getAvatarImage(): ?Medium + { + $avatars = $this->get('avatar'); + if (is_array($avatars) && $avatars) { + $avatar = array_shift($avatars); + + $media = $this->getMedia(); + $name = $avatar['name'] ?? null; + + $image = $name ? $media[$name] : null; + if ($image instanceof ImageMedium || + $image instanceof StaticImageMedium) { + return $image; + } + } + + return null; + } + + /** + * Return the User's avatar URL + * + * @return string + */ + public function getAvatarUrl(): string + { + // Try to locate avatar image. + $avatar = $this->getAvatarImage(); + if ($avatar) { + return $avatar->url(); + } + + // Try if avatar is a sting (URL). + $avatar = $this->get('avatar'); + if (is_string($avatar)) { + return $avatar; + } + + // Try looking for provider. + $provider = $this->get('provider'); + $provider_options = $this->get($provider); + if (is_array($provider_options)) { + if (isset($provider_options['avatar_url']) && is_string($provider_options['avatar_url'])) { + return $provider_options['avatar_url']; + } + if (isset($provider_options['avatar']) && is_string($provider_options['avatar'])) { + return $provider_options['avatar']; + } + } + + $email = $this->get('email'); + $avatar_generator = Grav::instance()['config']->get('system.accounts.avatar', 'multiavatar'); + if ($avatar_generator === 'gravatar') { + if (!$email) { + return ''; + } + + $hash = md5(strtolower(trim($email))); + + return 'https://www.gravatar.com/avatar/' . $hash; + } + + $hash = $this->get('avatar_hash'); + if (!$hash) { + $username = $this->get('username'); + $hash = md5(strtolower(trim($email ?? $username))); + } + + return $this->generateMultiavatar($hash); + } + + /** + * @param string $hash + * @return string + */ + protected function generateMultiavatar(string $hash): string + { + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + + $storage = $locator->findResource('image://multiavatar', true, true); + $avatar_file = "{$storage}/{$hash}.svg"; + + if (!file_exists($storage)) { + Folder::create($storage); + } + + if (!file_exists($avatar_file)) { + $mavatar = new Multiavatar(); + + file_put_contents($avatar_file, $mavatar->generate($hash, null, null)); + } + + $avatar_url = $locator->findResource("image://multiavatar/{$hash}.svg", false, true); + + return Utils::url($avatar_url); + + } + + abstract public function get($name, $default = null, $separator = null); + abstract public function set($name, $value, $separator = null); + abstract public function undef($name, $separator = null); + abstract public function save(); +} diff --git a/system/src/Grav/Common/User/User.php b/system/src/Grav/Common/User/User.php new file mode 100644 index 0000000..362a247 --- /dev/null +++ b/system/src/Grav/Common/User/User.php @@ -0,0 +1,144 @@ +exists(). + * + * @param string $username + * @return UserInterface + * @deprecated 1.6 Use $grav['accounts']->load(...) instead. + */ + public static function load($username) + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use $grav[\'accounts\']->' . __FUNCTION__ . '() instead', E_USER_DEPRECATED); + + return static::getCollection()->load($username); + } + + /** + * Find a user by username, email, etc + * + * Always creates user object. To check if user exists, use $this->exists(). + * + * @param string $query the query to search for + * @param array $fields the fields to search + * @return UserInterface + * @deprecated 1.6 Use $grav['accounts']->find(...) instead. + */ + public static function find($query, $fields = ['username', 'email']) + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use $grav[\'accounts\']->' . __FUNCTION__ . '() instead', E_USER_DEPRECATED); + + return static::getCollection()->find($query, $fields); + } + + /** + * Remove user account. + * + * @param string $username + * @return bool True if the action was performed + * @deprecated 1.6 Use $grav['accounts']->delete(...) instead. + */ + public static function remove($username) + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use $grav[\'accounts\']->delete() instead', E_USER_DEPRECATED); + + return static::getCollection()->delete($username); + } + + /** + * @return UserCollectionInterface + */ + protected static function getCollection() + { + return Grav::instance()['accounts']; + } + } +} else { + /** + * @deprecated 1.6 Use $grav['accounts'] instead of static calls. In type hints, use UserInterface. + */ + class User extends DataUser\User + { + /** + * Load user account. + * + * Always creates user object. To check if user exists, use $this->exists(). + * + * @param string $username + * @return UserInterface + * @deprecated 1.6 Use $grav['accounts']->load(...) instead. + */ + public static function load($username) + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use $grav[\'accounts\']->' . __FUNCTION__ . '() instead', E_USER_DEPRECATED); + + return static::getCollection()->load($username); + } + + /** + * Find a user by username, email, etc + * + * Always creates user object. To check if user exists, use $this->exists(). + * + * @param string $query the query to search for + * @param array $fields the fields to search + * @return UserInterface + * @deprecated 1.6 Use $grav['accounts']->find(...) instead. + */ + public static function find($query, $fields = ['username', 'email']) + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use $grav[\'accounts\']->' . __FUNCTION__ . '() instead', E_USER_DEPRECATED); + + return static::getCollection()->find($query, $fields); + } + + /** + * Remove user account. + * + * @param string $username + * @return bool True if the action was performed + * @deprecated 1.6 Use $grav['accounts']->delete(...) instead. + */ + public static function remove($username) + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use $grav[\'accounts\']->delete() instead', E_USER_DEPRECATED); + + return static::getCollection()->delete($username); + } + + /** + * @return UserCollectionInterface + */ + protected static function getCollection() + { + return Grav::instance()['accounts']; + } + } +} diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php new file mode 100644 index 0000000..815b708 --- /dev/null +++ b/system/src/Grav/Common/Utils.php @@ -0,0 +1,2227 @@ +schemeExists($scheme)) { + // If scheme does not exists as a stream, assume it's external. + return str_replace(' ', '%20', $input); + } + + // Attempt to find the resource (because of parse_url() we need to put host back to path). + $resource = $locator->findResource("{$scheme}://{$host}{$path}", false); + + if ($resource === false) { + if (!$fail_gracefully) { + return false; + } + + // Return location where the file would be if it was saved. + $resource = $locator->findResource("{$scheme}://{$host}{$path}", false, true); + } + } elseif ($host || $port) { + // If URL doesn't have scheme but has host or port, it is external. + return str_replace(' ', '%20', $input); + } + + if (!empty($resource)) { + // Add query string back. + if (isset($parts['query'])) { + $resource .= '?' . $parts['query']; + } + + // Add fragment back. + if (isset($parts['fragment'])) { + $resource .= '#' . $parts['fragment']; + } + } + } else { + // Not a valid URL (can still be a stream). + $resource = $locator->findResource($input, false); + } + } else { + // Just a path. + /** @var Pages $pages */ + $pages = $grav['pages']; + + // Is this a page? + $page = $pages->find($input, true); + if ($page && $page->routable()) { + return $page->url($domain); + } + + $root = preg_quote($uri->rootUrl(), '#'); + $pattern = '#(' . $root . '$|' . $root . '/)#'; + if (!empty($root) && preg_match($pattern, $input, $matches)) { + $input = static::replaceFirstOccurrence($matches[0], '', $input); + } + + $input = ltrim($input, '/'); + $resource = $input; + } + + if (!$fail_gracefully && $resource === false) { + return false; + } + + $domain = $domain ?: $grav['config']->get('system.absolute_urls', false); + + return rtrim($uri->rootUrl($domain), '/') . '/' . ($resource ?: ''); + } + + /** + * Helper method to find the full path to a file, be it a stream, a relative path, or + * already a full path + * + * @param string $path + * @return string + */ + public static function fullPath($path) + { + $locator = Grav::instance()['locator']; + + if ($locator->isStream($path)) { + $path = $locator->findResource($path, true); + } elseif (!static::startsWith($path, GRAV_ROOT)) { + $base_url = Grav::instance()['base_url']; + $path = GRAV_ROOT . '/' . ltrim(static::replaceFirstOccurrence($base_url, '', $path), '/'); + } + + return $path; + } + + + /** + * Check if the $haystack string starts with the substring $needle + * + * @param string $haystack + * @param string|string[] $needle + * @param bool $case_sensitive + * @return bool + */ + public static function startsWith($haystack, $needle, $case_sensitive = true) + { + $status = false; + + $compare_func = $case_sensitive ? 'mb_strpos' : 'mb_stripos'; + + foreach ((array)$needle as $each_needle) { + $status = $each_needle === '' || $compare_func((string) $haystack, $each_needle) === 0; + if ($status) { + break; + } + } + + return $status; + } + + /** + * Check if the $haystack string ends with the substring $needle + * + * @param string $haystack + * @param string|string[] $needle + * @param bool $case_sensitive + * @return bool + */ + public static function endsWith($haystack, $needle, $case_sensitive = true) + { + $status = false; + + $compare_func = $case_sensitive ? 'mb_strrpos' : 'mb_strripos'; + + foreach ((array)$needle as $each_needle) { + $expectedPosition = mb_strlen((string) $haystack) - mb_strlen($each_needle); + $status = $each_needle === '' || $compare_func((string) $haystack, $each_needle, 0) === $expectedPosition; + if ($status) { + break; + } + } + + return $status; + } + + /** + * Check if the $haystack string contains the substring $needle + * + * @param string $haystack + * @param string|string[] $needle + * @param bool $case_sensitive + * @return bool + */ + public static function contains($haystack, $needle, $case_sensitive = true) + { + $status = false; + + $compare_func = $case_sensitive ? 'mb_strpos' : 'mb_stripos'; + + foreach ((array)$needle as $each_needle) { + $status = $each_needle === '' || $compare_func((string) $haystack, $each_needle) !== false; + if ($status) { + break; + } + } + + return $status; + } + + /** + * Function that can match wildcards + * + * match_wildcard('foo*', $test), // TRUE + * match_wildcard('bar*', $test), // FALSE + * match_wildcard('*bar*', $test), // TRUE + * match_wildcard('**blob**', $test), // TRUE + * match_wildcard('*a?d*', $test), // TRUE + * match_wildcard('*etc**', $test) // TRUE + * + * @param string $wildcard_pattern + * @param string $haystack + * @return false|int + */ + public static function matchWildcard($wildcard_pattern, $haystack) + { + $regex = str_replace( + array("\*", "\?"), // wildcard chars + array('.*', '.'), // regexp chars + preg_quote($wildcard_pattern, '/') + ); + + return preg_match('/^' . $regex . '$/is', $haystack); + } + + /** + * Render simple template filling up the variables in it. If value is not defined, leave it as it was. + * + * @param string $template Template string + * @param array $variables Variables with values + * @param array $brackets Optional array of opening and closing brackets or symbols + * @return string Final string filled with values + */ + public static function simpleTemplate(string $template, array $variables, array $brackets = ['{', '}']): string + { + $opening = $brackets[0] ?? '{'; + $closing = $brackets[1] ?? '}'; + $expression = '/' . preg_quote($opening, '/') . '(.*?)' . preg_quote($closing, '/') . '/'; + $callback = static function ($match) use ($variables) { + return $variables[$match[1]] ?? $match[0]; + }; + + return preg_replace_callback($expression, $callback, $template); + } + + /** + * Returns the substring of a string up to a specified needle. if not found, return the whole haystack + * + * @param string $haystack + * @param string $needle + * @param bool $case_sensitive + * + * @return string + */ + public static function substrToString($haystack, $needle, $case_sensitive = true) + { + $compare_func = $case_sensitive ? 'mb_strpos' : 'mb_stripos'; + + if (static::contains($haystack, $needle, $case_sensitive)) { + return mb_substr($haystack, 0, $compare_func($haystack, $needle, $case_sensitive)); + } + + return $haystack; + } + + /** + * Utility method to replace only the first occurrence in a string + * + * @param string $search + * @param string $replace + * @param string $subject + * + * @return string + */ + public static function replaceFirstOccurrence($search, $replace, $subject) + { + if (!$search) { + return $subject; + } + + $pos = mb_strpos($subject, $search); + if ($pos !== false) { + $subject = static::mb_substr_replace($subject, $replace, $pos, mb_strlen($search)); + } + + + return $subject; + } + + /** + * Utility method to replace only the last occurrence in a string + * + * @param string $search + * @param string $replace + * @param string $subject + * @return string + */ + public static function replaceLastOccurrence($search, $replace, $subject) + { + $pos = strrpos($subject, $search); + + if ($pos !== false) { + $subject = static::mb_substr_replace($subject, $replace, $pos, mb_strlen($search)); + } + + return $subject; + } + + /** + * Multibyte compatible substr_replace + * + * @param string $original + * @param string $replacement + * @param int $position + * @param int $length + * @return string + */ + public static function mb_substr_replace($original, $replacement, $position, $length) + { + $startString = mb_substr($original, 0, $position, 'UTF-8'); + $endString = mb_substr($original, $position + $length, mb_strlen($original), 'UTF-8'); + + return $startString . $replacement . $endString; + } + + /** + * Merge two objects into one. + * + * @param object $obj1 + * @param object $obj2 + * + * @return object + */ + public static function mergeObjects($obj1, $obj2) + { + return (object)array_merge((array)$obj1, (array)$obj2); + } + + /** + * @param array $array + * @return bool + */ + public static function isAssoc(array $array) + { + return (array_values($array) !== $array); + } + + /** + * Lowercase an entire array. Useful when combined with `in_array()` + * + * @param array $a + * @return array|false + */ + public static function arrayLower(array $a) + { + return array_map('mb_strtolower', $a); + } + + /** + * Simple function to remove item/s in an array by value + * + * @param array $search + * @param string|array $value + * @return array + */ + public static function arrayRemoveValue(array $search, $value) + { + foreach ((array)$value as $val) { + $key = array_search($val, $search); + if ($key !== false) { + unset($search[$key]); + } + } + return $search; + } + + /** + * Recursive Merge with uniqueness + * + * @param array $array1 + * @param array $array2 + * @return array + */ + public static function arrayMergeRecursiveUnique($array1, $array2) + { + if (empty($array1)) { + // Optimize the base case + return $array2; + } + + foreach ($array2 as $key => $value) { + if (is_array($value) && isset($array1[$key]) && is_array($array1[$key])) { + $value = static::arrayMergeRecursiveUnique($array1[$key], $value); + } + $array1[$key] = $value; + } + + return $array1; + } + + /** + * Returns an array with the differences between $array1 and $array2 + * + * @param array $array1 + * @param array $array2 + * @return array + */ + public static function arrayDiffMultidimensional($array1, $array2) + { + $result = array(); + foreach ($array1 as $key => $value) { + if (!is_array($array2) || !array_key_exists($key, $array2)) { + $result[$key] = $value; + continue; + } + if (is_array($value)) { + $recursiveArrayDiff = static::ArrayDiffMultidimensional($value, $array2[$key]); + if (count($recursiveArrayDiff)) { + $result[$key] = $recursiveArrayDiff; + } + continue; + } + if ($value != $array2[$key]) { + $result[$key] = $value; + } + } + + return $result; + } + + /** + * Array combine but supports different array lengths + * + * @param array $arr1 + * @param array $arr2 + * @return array|false + */ + public static function arrayCombine($arr1, $arr2) + { + $count = min(count($arr1), count($arr2)); + + return array_combine(array_slice($arr1, 0, $count), array_slice($arr2, 0, $count)); + } + + /** + * Array is associative or not + * + * @param array $arr + * @return bool + */ + public static function arrayIsAssociative($arr) + { + if ([] === $arr) { + return false; + } + + return array_keys($arr) !== range(0, count($arr) - 1); + } + + /** + * Return the Grav date formats allowed + * + * @return array + */ + public static function dateFormats() + { + $now = new DateTime(); + + $date_formats = [ + 'd-m-Y H:i' => 'd-m-Y H:i (e.g. ' . $now->format('d-m-Y H:i') . ')', + 'Y-m-d H:i' => 'Y-m-d H:i (e.g. ' . $now->format('Y-m-d H:i') . ')', + 'm/d/Y h:i a' => 'm/d/Y h:i a (e.g. ' . $now->format('m/d/Y h:i a') . ')', + 'H:i d-m-Y' => 'H:i d-m-Y (e.g. ' . $now->format('H:i d-m-Y') . ')', + 'h:i a m/d/Y' => 'h:i a m/d/Y (e.g. ' . $now->format('h:i a m/d/Y') . ')', + ]; + $default_format = Grav::instance()['config']->get('system.pages.dateformat.default'); + if ($default_format) { + $date_formats = array_merge([$default_format => $default_format . ' (e.g. ' . $now->format($default_format) . ')'], $date_formats); + } + + return $date_formats; + } + + /** + * Get current date/time + * + * @param string|null $default_format + * @return string + * @throws Exception + */ + public static function dateNow($default_format = null) + { + $now = new DateTime(); + + if (null === $default_format) { + $default_format = Grav::instance()['config']->get('system.pages.dateformat.default'); + } + + return $now->format($default_format); + } + + /** + * Truncate text by number of characters but can cut off words. + * + * @param string $string + * @param int $limit Max number of characters. + * @param bool $up_to_break truncate up to breakpoint after char count + * @param string $break Break point. + * @param string $pad Appended padding to the end of the string. + * @return string + */ + public static function truncate($string, $limit = 150, $up_to_break = false, $break = ' ', $pad = '…') + { + // return with no change if string is shorter than $limit + if (mb_strlen($string) <= $limit) { + return $string; + } + + // is $break present between $limit and the end of the string? + if ($up_to_break && false !== ($breakpoint = mb_strpos($string, $break, $limit))) { + if ($breakpoint < mb_strlen($string) - 1) { + $string = mb_substr($string, 0, $breakpoint) . $pad; + } + } else { + $string = mb_substr($string, 0, $limit) . $pad; + } + + return $string; + } + + /** + * Truncate text by number of characters in a "word-safe" manor. + * + * @param string $string + * @param int $limit + * @return string + */ + public static function safeTruncate($string, $limit = 150) + { + return static::truncate($string, $limit, true); + } + + + /** + * Truncate HTML by number of characters. not "word-safe"! + * + * @param string $text + * @param int $length in characters + * @param string $ellipsis + * @return string + */ + public static function truncateHtml($text, $length = 100, $ellipsis = '...') + { + return Truncator::truncateLetters($text, $length, $ellipsis); + } + + /** + * Truncate HTML by number of characters in a "word-safe" manor. + * + * @param string $text + * @param int $length in words + * @param string $ellipsis + * @return string + */ + public static function safeTruncateHtml($text, $length = 25, $ellipsis = '...') + { + return Truncator::truncateWords($text, $length, $ellipsis); + } + + /** + * Generate a random string of a given length + * + * @param int $length + * @return string + */ + public static function generateRandomString($length = 5) + { + return substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, $length); + } + + /** + * Generates a random string with configurable length, prefix and suffix. + * Unlike the built-in `uniqid()`, this string is non-conflicting and safe + * + * @param int $length + * @param array $options + * @return string + * @throws Exception + */ + public static function uniqueId(int $length = 13, array $options = []): string + { + $options = array_merge(['prefix' => '', 'suffix' => ''], $options); + $bytes = random_bytes(ceil($length / 2)); + + return $options['prefix'] . substr(bin2hex($bytes), 0, $length) . $options['suffix']; + } + + /** + * Provides the ability to download a file to the browser + * + * @param string $file the full path to the file to be downloaded + * @param bool $force_download as opposed to letting browser choose if to download or render + * @param int $sec Throttling, try 0.1 for some speed throttling of downloads + * @param int $bytes Size of chunks to send in bytes. Default is 1024 + * @param array $options Extra options: [mime, download_name, expires] + * @throws Exception + */ + public static function download($file, $force_download = true, $sec = 0, $bytes = 1024, array $options = []) + { + $grav = Grav::instance(); + + if (file_exists($file)) { + // fire download event + $grav->fireEvent('onBeforeDownload', new Event(['file' => $file, 'options' => &$options])); + + $file_parts = static::pathinfo($file); + $mimetype = $options['mime'] ?? static::getMimeByExtension($file_parts['extension']); + $size = filesize($file); // File size + + $grav->cleanOutputBuffers(); + + // required for IE, otherwise Content-Disposition may be ignored + if (ini_get('zlib.output_compression')) { + ini_set('zlib.output_compression', 'Off'); + } + + header('Content-Type: ' . $mimetype); + header('Accept-Ranges: bytes'); + + if ($force_download) { + // output the regular HTTP headers + header('Content-Disposition: attachment; filename="' . ($options['download_name'] ?? $file_parts['basename']) . '"'); + } + + // multipart-download and download resuming support + if (isset($_SERVER['HTTP_RANGE'])) { + [$a, $range] = explode('=', $_SERVER['HTTP_RANGE'], 2); + [$range] = explode(',', $range, 2); + [$range, $range_end] = explode('-', $range); + $range = (int)$range; + if (!$range_end) { + $range_end = $size - 1; + } else { + $range_end = (int)$range_end; + } + $new_length = $range_end - $range + 1; + header('HTTP/1.1 206 Partial Content'); + header("Content-Length: {$new_length}"); + header("Content-Range: bytes {$range}-{$range_end}/{$size}"); + } else { + $range = 0; + $new_length = $size; + header('Content-Length: ' . $size); + + if ($grav['config']->get('system.cache.enabled')) { + $expires = $options['expires'] ?? $grav['config']->get('system.pages.expires'); + if ($expires > 0) { + $expires_date = gmdate('D, d M Y H:i:s T', time() + $expires); + header('Cache-Control: max-age=' . $expires); + header('Expires: ' . $expires_date); + header('Pragma: cache'); + } + header('Last-Modified: ' . gmdate('D, d M Y H:i:s T', filemtime($file))); + + // Return 304 Not Modified if the file is already cached in the browser + if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && + strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= filemtime($file)) { + header('HTTP/1.1 304 Not Modified'); + exit(); + } + } + } + + /* output the file itself */ + $chunksize = $bytes * 8; //you may want to change this + $bytes_send = 0; + + $fp = @fopen($file, 'rb'); + if ($fp) { + if ($range) { + fseek($fp, $range); + } + while (!feof($fp) && (!connection_aborted()) && ($bytes_send < $new_length)) { + $buffer = fread($fp, $chunksize); + echo($buffer); //echo($buffer); // is also possible + flush(); + usleep($sec * 1000000); + $bytes_send += strlen($buffer); + } + fclose($fp); + } else { + throw new RuntimeException('Error - can not open file.'); + } + + exit; + } + } + + /** + * Returns the output render format, usually the extension provided in the URL. (e.g. `html`, `json`, `xml`, etc). + * + * @return string + */ + public static function getPageFormat(): string + { + /** @var Uri $uri */ + $uri = Grav::instance()['uri']; + + // Set from uri extension + $uri_extension = $uri->extension(); + if (is_string($uri_extension) && $uri->isValidExtension($uri_extension)) { + return ($uri_extension); + } + + // Use content negotiation via the `accept:` header + $http_accept = $_SERVER['HTTP_ACCEPT'] ?? null; + if (is_string($http_accept)) { + $negotiator = new Negotiator(); + + $supported_types = static::getSupportPageTypes(['html', 'json']); + $priorities = static::getMimeTypes($supported_types); + + $media_type = $negotiator->getBest($http_accept, $priorities); + $mimetype = $media_type instanceof Accept ? $media_type->getValue() : ''; + + return static::getExtensionByMime($mimetype); + } + + return 'html'; + } + + /** + * Return the mimetype based on filename extension + * + * @param string $extension Extension of file (eg "txt") + * @param string $default + * @return string + */ + public static function getMimeByExtension($extension, $default = 'application/octet-stream') + { + $extension = strtolower($extension); + + // look for some standard types + switch ($extension) { + case null: + return $default; + 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'; + } + + $media_types = Grav::instance()['config']->get('media.types'); + + return $media_types[$extension]['mime'] ?? $default; + } + + /** + * Get all the mimetypes for an array of extensions + * + * @param array $extensions + * @return array + */ + public static function getMimeTypes(array $extensions) + { + $mimetypes = []; + foreach ($extensions as $extension) { + $mimetype = static::getMimeByExtension($extension, false); + if ($mimetype && !in_array($mimetype, $mimetypes)) { + $mimetypes[] = $mimetype; + } + } + return $mimetypes; + } + + /** + * Return all extensions for given mimetype. The first extension is the default one. + * + * @param string $mime Mime type (eg 'image/jpeg') + * @return string[] List of extensions eg. ['jpg', 'jpe', 'jpeg'] + */ + public static function getExtensionsByMime($mime) + { + $mime = strtolower($mime); + + $media_types = (array)Grav::instance()['config']->get('media.types'); + + $list = []; + foreach ($media_types as $extension => $type) { + if ($extension === '' || $extension === 'defaults') { + continue; + } + + if (isset($type['mime']) && $type['mime'] === $mime) { + $list[] = $extension; + } + } + + return $list; + } + + /** + * Return the mimetype based on filename extension + * + * @param string $mime mime type (eg "text/html") + * @param string $default default value + * @return string + */ + public static function getExtensionByMime($mime, $default = 'html') + { + $mime = strtolower($mime); + + // look for some standard mime types + switch ($mime) { + case '*/*': + case 'text/*': + case 'text/html': + return 'html'; + case 'application/json': + return 'json'; + case 'application/atom+xml': + return 'atom'; + case 'application/rss+xml': + return 'rss'; + case 'application/xml': + return 'xml'; + } + + $media_types = (array)Grav::instance()['config']->get('media.types'); + + foreach ($media_types as $extension => $type) { + if ($extension === 'defaults') { + continue; + } + if (isset($type['mime']) && $type['mime'] === $mime) { + return $extension; + } + } + + return $default; + } + + /** + * Get all the extensions for an array of mimetypes + * + * @param array $mimetypes + * @return array + */ + public static function getExtensions(array $mimetypes) + { + $extensions = []; + foreach ($mimetypes as $mimetype) { + $extension = static::getExtensionByMime($mimetype, false); + if ($extension && !in_array($extension, $extensions, true)) { + $extensions[] = $extension; + } + } + + return $extensions; + } + + /** + * Return the mimetype based on filename + * + * @param string $filename Filename or path to file + * @param string $default default value + * @return string + */ + public static function getMimeByFilename($filename, $default = 'application/octet-stream') + { + return static::getMimeByExtension(static::pathinfo($filename, PATHINFO_EXTENSION), $default); + } + + /** + * Return the mimetype based on existing local file + * + * @param string $filename Path to the file + * @param string $default + * @return string|bool + */ + public static function getMimeByLocalFile($filename, $default = 'application/octet-stream') + { + $type = false; + + // For local files we can detect type by the file content. + if (!stream_is_local($filename) || !file_exists($filename)) { + return false; + } + + // Prefer using finfo if it exists. + if (extension_loaded('fileinfo')) { + $finfo = finfo_open(FILEINFO_SYMLINK | FILEINFO_MIME_TYPE); + $type = finfo_file($finfo, $filename); + finfo_close($finfo); + } else { + // Fall back to use getimagesize() if it is available (not recommended, but better than nothing) + $info = @getimagesize($filename); + if ($info) { + $type = $info['mime']; + } + } + + return $type ?: static::getMimeByFilename($filename, $default); + } + + + /** + * Returns true if filename is considered safe. + * + * @param string $filename + * @return bool + */ + public static function checkFilename($filename): bool + { + $dangerous_extensions = Grav::instance()['config']->get('security.uploads_dangerous_extensions', []); + $extension = mb_strtolower(static::pathinfo($filename, PATHINFO_EXTENSION)); + + return !( + // Empty filenames are not allowed. + !$filename + // Filename should not contain horizontal/vertical tabs, newlines, nils or back/forward slashes. + || strtr($filename, "\t\v\n\r\0\\/", '_______') !== $filename + // Filename should not start or end with dot or space. + || trim($filename, '. ') !== $filename + // Filename should not contain path traversal + || str_replace('..', '', $filename) !== $filename + // File extension should not be part of configured dangerous extensions + || in_array($extension, $dangerous_extensions) + ); + } + + /** + * Unicode-safe version of PHP’s pathinfo() function. + * + * @link https://www.php.net/manual/en/function.pathinfo.php + * + * @param string $path + * @param int|null $flags + * @return array|string + */ + public static function pathinfo($path, int $flags = null) + { + $path = str_replace(['%2F', '%5C'], ['/', '\\'], rawurlencode($path)); + + if (null === $flags) { + $info = pathinfo($path); + } else { + $info = pathinfo($path, $flags); + } + + if (is_array($info)) { + return array_map('rawurldecode', $info); + } + + return rawurldecode($info); + } + + /** + * Unicode-safe version of the PHP basename() function. + * + * @link https://www.php.net/manual/en/function.basename.php + * + * @param string $path + * @param string $suffix + * @return string + */ + public static function basename($path, string $suffix = ''): string + { + return rawurldecode(basename(str_replace(['%2F', '%5C'], '/', rawurlencode($path)), $suffix)); + } + + /** + * Normalize path by processing relative `.` and `..` syntax and merging path + * + * @param string $path + * @return string + */ + public static function normalizePath($path) + { + // Resolve any streams + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + if ($locator->isStream($path)) { + $path = $locator->findResource($path); + } + + // Set root properly for any URLs + $root = ''; + preg_match(self::ROOTURL_REGEX, $path, $matches); + if ($matches) { + $root = $matches[1]; + $path = $matches[2]; + } + + // Strip off leading / to ensure explode is accurate + if (static::startsWith($path, '/')) { + $root .= '/'; + $path = ltrim($path, '/'); + } + + // If there are any relative paths (..) handle those + if (static::contains($path, '..')) { + $segments = explode('/', trim($path, '/')); + $ret = []; + foreach ($segments as $segment) { + if (($segment === '.') || $segment === '') { + continue; + } + if ($segment === '..') { + array_pop($ret); + } else { + $ret[] = $segment; + } + } + $path = implode('/', $ret); + } + + // Stick everything back together + $normalized = $root . $path; + + return $normalized; + } + + /** + * Check whether a function exists. + * + * Disabled functions count as non-existing functions, just like in PHP 8+. + * + * @param string $function the name of the function to check + * @return bool + */ + public static function functionExists($function): bool + { + if (!function_exists($function)) { + return false; + } + + // In PHP 7 we need to also exclude disabled methods. + return !static::isFunctionDisabled($function); + } + + /** + * Check whether a function is disabled in the PHP settings + * + * @param string $function the name of the function to check + * @return bool + */ + public static function isFunctionDisabled($function): bool + { + static $list; + + if (null === $list) { + $str = trim(ini_get('disable_functions') . ',' . ini_get('suhosin.executor.func.blacklist'), ','); + $list = $str ? array_flip(preg_split('/\s*,\s*/', $str)) : []; + } + + return array_key_exists($function, $list); + } + + /** + * Get the formatted timezones list + * + * @return array + */ + public static function timezones() + { + $timezones = DateTimeZone::listIdentifiers(DateTimeZone::ALL); + $offsets = []; + $testDate = new DateTime(); + + foreach ($timezones as $zone) { + $tz = new DateTimeZone($zone); + $offsets[$zone] = $tz->getOffset($testDate); + } + + asort($offsets); + + $timezone_list = []; + foreach ($offsets as $timezone => $offset) { + $offset_prefix = $offset < 0 ? '-' : '+'; + $offset_formatted = gmdate('H:i', abs($offset)); + + $pretty_offset = "UTC{$offset_prefix}{$offset_formatted}"; + + $timezone_list[$timezone] = "({$pretty_offset}) " . str_replace('_', ' ', $timezone); + } + + return $timezone_list; + } + + /** + * Recursively filter an array, filtering values by processing them through the $fn function argument + * + * @param array $source the Array to filter + * @param callable $fn the function to pass through each array item + * @return array + */ + public static function arrayFilterRecursive(array $source, $fn) + { + $result = []; + foreach ($source as $key => $value) { + if (is_array($value)) { + $result[$key] = static::arrayFilterRecursive($value, $fn); + continue; + } + if ($fn($key, $value)) { + $result[$key] = $value; // KEEP + continue; + } + } + + return $result; + } + + /** + * Flatten a multi-dimensional associative array into query params. + * + * @param array $array + * @param string $prepend + * @return array + */ + public static function arrayToQueryParams($array, $prepend = '') + { + $results = []; + foreach ($array as $key => $value) { + $name = $prepend ? $prepend . '[' . $key . ']' : $key; + + if (is_array($value)) { + $results = array_merge($results, static::arrayToQueryParams($value, $name)); + } else { + $results[$name] = $value; + } + } + + return $results; + } + + /** + * Flatten an array + * + * @param array $array + * @return array + */ + public static function arrayFlatten($array) + { + $flatten = []; + foreach ($array as $key => $inner) { + if (is_array($inner)) { + foreach ($inner as $inner_key => $value) { + $flatten[$inner_key] = $value; + } + } else { + $flatten[$key] = $inner; + } + } + + return $flatten; + } + + /** + * Flatten a multi-dimensional associative array into dot notation + * + * @param array $array + * @param string $prepend + * @return array + */ + public static function arrayFlattenDotNotation($array, $prepend = '') + { + $results = array(); + foreach ($array as $key => $value) { + if (is_array($value)) { + $results = array_merge($results, static::arrayFlattenDotNotation($value, $prepend . $key . '.')); + } else { + $results[$prepend . $key] = $value; + } + } + + return $results; + } + + /** + * Opposite of flatten, convert flat dot notation array to multi dimensional array. + * + * If any of the parent has a scalar value, all children get ignored: + * + * admin.pages=true + * admin.pages.read=true + * + * becomes + * + * admin: + * pages: true + * + * @param array $array + * @param string $separator + * @return array + */ + public static function arrayUnflattenDotNotation($array, $separator = '.') + { + $newArray = []; + foreach ($array as $key => $value) { + $dots = explode($separator, $key); + if (count($dots) > 1) { + $last = &$newArray[$dots[0]]; + foreach ($dots as $k => $dot) { + if ($k === 0) { + continue; + } + + // Cannot use a scalar value as an array + if (null !== $last && !is_array($last)) { + continue 2; + } + + $last = &$last[$dot]; + } + + // Cannot use a scalar value as an array + if (null !== $last && !is_array($last)) { + continue; + } + + $last = $value; + } else { + $newArray[$key] = $value; + } + } + + return $newArray; + } + + /** + * Checks if the passed path contains the language code prefix + * + * @param string $string The path + * + * @return bool|string Either false or the language + * + */ + public static function pathPrefixedByLangCode($string) + { + $languages_enabled = Grav::instance()['config']->get('system.languages.supported', []); + $parts = explode('/', trim($string, '/')); + + if (count($parts) > 0 && in_array($parts[0], $languages_enabled)) { + return $parts[0]; + } + return false; + } + + /** + * Get the timestamp of a date + * + * @param string $date a String expressed in the system.pages.dateformat.default format, with fallback to a + * strtotime argument + * @param string|null $format a date format to use if possible + * @return int the timestamp + */ + public static function date2timestamp($date, $format = null) + { + $config = Grav::instance()['config']; + $dateformat = $format ?: $config->get('system.pages.dateformat.default'); + + // try to use DateTime and default format + if ($dateformat) { + $datetime = DateTime::createFromFormat($dateformat, $date); + } else { + try { + $datetime = new DateTime($date); + } catch (Exception $e) { + $datetime = false; + } + } + + // fallback to strtotime() if DateTime approach failed + if ($datetime !== false) { + return $datetime->getTimestamp(); + } + + return strtotime($date); + } + + /** + * @param array $array + * @param string $path + * @param null $default + * @return mixed + * + * @deprecated 1.5 Use ->getDotNotation() method instead. + */ + public static function resolve(array $array, $path, $default = null) + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use ->getDotNotation() method instead', E_USER_DEPRECATED); + + return static::getDotNotation($array, $path, $default); + } + + /** + * Checks if a value is positive (true) + * + * @param string $value + * @return bool + */ + public static function isPositive($value) + { + return in_array($value, [true, 1, '1', 'yes', 'on', 'true'], true); + } + + /** + * Checks if a value is negative (false) + * + * @param string $value + * @return bool + */ + public static function isNegative($value) + { + return in_array($value, [false, 0, '0', 'no', 'off', 'false'], true); + } + + /** + * Generates a nonce string to be hashed. Called by self::getNonce() + * We removed the IP portion in this version because it causes too many inconsistencies + * with reverse proxy setups. + * + * @param string $action + * @param bool $previousTick if true, generates the token for the previous tick (the previous 12 hours) + * @return string the nonce string + */ + private static function generateNonceString($action, $previousTick = false) + { + $grav = Grav::instance(); + + $username = isset($grav['user']) ? $grav['user']->username : ''; + $token = session_id(); + $i = self::nonceTick(); + + if ($previousTick) { + $i--; + } + + return ($i . '|' . $action . '|' . $username . '|' . $token . '|' . $grav['config']->get('security.salt')); + } + + /** + * Get the time-dependent variable for nonce creation. + * + * Now a tick lasts a day. Once the day is passed, the nonce is not valid any more. Find a better way + * to ensure nonces issued near the end of the day do not expire in that small amount of time + * + * @return int the time part of the nonce. Changes once every 24 hours + */ + private static function nonceTick() + { + $secondsInHalfADay = 60 * 60 * 12; + + return (int)ceil(time() / $secondsInHalfADay); + } + + /** + * Creates a hashed nonce tied to the passed action. Tied to the current user and time. The nonce for a given + * action is the same for 12 hours. + * + * @param string $action the action the nonce is tied to (e.g. save-user-admin or move-page-homepage) + * @param bool $previousTick if true, generates the token for the previous tick (the previous 12 hours) + * @return string the nonce + */ + public static function getNonce($action, $previousTick = false) + { + // Don't regenerate this again if not needed + if (isset(static::$nonces[$action][$previousTick])) { + return static::$nonces[$action][$previousTick]; + } + $nonce = md5(self::generateNonceString($action, $previousTick)); + static::$nonces[$action][$previousTick] = $nonce; + + return static::$nonces[$action][$previousTick]; + } + + /** + * Verify the passed nonce for the give action + * + * @param string|string[] $nonce the nonce to verify + * @param string $action the action to verify the nonce to + * @return boolean verified or not + */ + public static function verifyNonce($nonce, $action) + { + //Safety check for multiple nonces + if (is_array($nonce)) { + $nonce = array_shift($nonce); + } + + //Nonce generated 0-12 hours ago + if ($nonce === self::getNonce($action)) { + return true; + } + + //Nonce generated 12-24 hours ago + return $nonce === self::getNonce($action, true); + } + + /** + * Simple helper method to get whether or not the admin plugin is active + * + * @return bool + */ + public static function isAdminPlugin() + { + return isset(Grav::instance()['admin']); + } + + /** + * Get a portion of an array (passed by reference) with dot-notation key + * + * @param array $array + * @param string|int|null $key + * @param null $default + * @return mixed + */ + public static function getDotNotation($array, $key, $default = null) + { + if (null === $key) { + return $array; + } + + if (isset($array[$key])) { + return $array[$key]; + } + + foreach (explode('.', $key) as $segment) { + if (!is_array($array) || !array_key_exists($segment, $array)) { + return $default; + } + + $array = $array[$segment]; + } + + return $array; + } + + /** + * Set portion of array (passed by reference) for a dot-notation key + * and set the value + * + * @param array $array + * @param string|int|null $key + * @param mixed $value + * @param bool $merge + * + * @return mixed + */ + public static function setDotNotation(&$array, $key, $value, $merge = false) + { + if (null === $key) { + return $array = $value; + } + + $keys = explode('.', $key); + + while (count($keys) > 1) { + $key = array_shift($keys); + + if (!isset($array[$key]) || !is_array($array[$key])) { + $array[$key] = array(); + } + + $array =& $array[$key]; + } + + $key = array_shift($keys); + + if (!$merge || !isset($array[$key])) { + $array[$key] = $value; + } else { + $array[$key] = array_merge($array[$key], $value); + } + + return $array; + } + + /** + * Utility method to determine if the current OS is Windows + * + * @return bool + */ + public static function isWindows() + { + return strncasecmp(PHP_OS, 'WIN', 3) === 0; + } + + /** + * Utility to determine if the server running PHP is Apache + * + * @return bool + */ + public static function isApache() + { + return isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== false; + } + + /** + * Sort a multidimensional array by another array of ordered keys + * + * @param array $array + * @param array $orderArray + * @return array + */ + public static function sortArrayByArray(array $array, array $orderArray) + { + $ordered = []; + foreach ($orderArray as $key) { + if (array_key_exists($key, $array)) { + $ordered[$key] = $array[$key]; + unset($array[$key]); + } + } + return $ordered + $array; + } + + /** + * Sort an array by a key value in the array + * + * @param mixed $array + * @param string|int $array_key + * @param int $direction + * @param int $sort_flags + * @return array + */ + public static function sortArrayByKey($array, $array_key, $direction = SORT_DESC, $sort_flags = SORT_REGULAR) + { + $output = []; + + if (!is_array($array) || !$array) { + return $output; + } + + foreach ($array as $key => $row) { + $output[$key] = $row[$array_key]; + } + + array_multisort($output, $direction, $sort_flags, $array); + + return $array; + } + + /** + * Get relative page path based on a token. + * + * @param string $path + * @param PageInterface|null $page + * @return string + * @throws RuntimeException + */ + public static function getPagePathFromToken($path, PageInterface $page = null) + { + return static::getPathFromToken($path, $page); + } + + /** + * Get relative path based on a token. + * + * Path supports following syntaxes: + * + * 'self@', 'self@/path' + * 'page@:/route', 'page@:/route/filename.ext' + * 'theme@:', 'theme@:/path' + * + * @param string $path + * @param FlexObjectInterface|PageInterface|null $object + * @return string + * @throws RuntimeException + */ + public static function getPathFromToken($path, $object = null) + { + $matches = static::resolveTokenPath($path); + if (null === $matches) { + return $path; + } + + $grav = Grav::instance(); + + switch ($matches[0]) { + case 'self': + if (!$object instanceof MediaInterface) { + throw new RuntimeException(sprintf('Page not available for self@ reference: %s', $path)); + } + + if ($matches[2] === '') { + if ($object->exists()) { + $route = '/' . $matches[1]; + + if ($object instanceof PageInterface) { + return trim($object->relativePagePath() . $route, '/'); + } + + $folder = $object->getMediaFolder(); + if ($folder) { + return trim($folder . $route, '/'); + } + } else { + return ''; + } + } + + break; + case 'page': + if ($matches[1] === '') { + $route = '/' . $matches[2]; + + // Exclude filename from the page lookup. + if (static::pathinfo($route, PATHINFO_EXTENSION)) { + $basename = '/' . static::basename($route); + $route = \dirname($route); + } else { + $basename = ''; + } + + $key = trim($route === '/' ? $grav['config']->get('system.home.alias') : $route, '/'); + if ($object instanceof PageObject) { + $object = $object->getFlexDirectory()->getObject($key); + } elseif (static::isAdminPlugin()) { + /** @var Flex|null $flex */ + $flex = $grav['flex'] ?? null; + $object = $flex ? $flex->getObject($key, 'pages') : null; + } else { + /** @var Pages $pages */ + $pages = $grav['pages']; + $object = $pages->find($route); + } + + if ($object instanceof PageInterface) { + return trim($object->relativePagePath() . $basename, '/'); + } + } + + break; + case 'theme': + if ($matches[1] === '') { + $route = '/' . $matches[2]; + $theme = $grav['locator']->findResource('theme://', false); + if (false !== $theme) { + return trim($theme . $route, '/'); + } + } + + break; + } + + throw new RuntimeException(sprintf('Token path not found: %s', $path)); + } + + /** + * Returns [token, route, path] from '@token/route:/path'. Route and path are optional. If pattern does not match, return null. + * + * @param string $path + * @return string[]|null + */ + protected static function resolveTokenPath(string $path): ?array + { + if (strpos($path, '@') !== false) { + $regex = '/^(@\w+|\w+@|@\w+@)([^:]*)(.*)$/u'; + if (preg_match($regex, $path, $matches)) { + return [ + trim($matches[1], '@'), + trim($matches[2], '/'), + trim($matches[3], ':/') + ]; + } + } + + return null; + } + + /** + * @return int + */ + public static function getUploadLimit() + { + static $max_size = -1; + + if ($max_size < 0) { + $post_max_size = static::parseSize(ini_get('post_max_size')); + if ($post_max_size > 0) { + $max_size = $post_max_size; + } else { + $max_size = 0; + } + + $upload_max = static::parseSize(ini_get('upload_max_filesize')); + if ($upload_max > 0 && $upload_max < $max_size) { + $max_size = $upload_max; + } + } + + return $max_size; + } + + /** + * Convert bytes to the unit specified by the $to parameter. + * + * @param int $bytes The filesize in Bytes. + * @param string $to The unit type to convert to. Accepts K, M, or G for Kilobytes, Megabytes, or Gigabytes, respectively. + * @param int $decimal_places The number of decimal places to return. + * @return int Returns only the number of units, not the type letter. Returns 0 if the $to unit type is out of scope. + * + */ + public static function convertSize($bytes, $to, $decimal_places = 1) + { + $formulas = array( + 'K' => number_format($bytes / 1024, $decimal_places), + 'M' => number_format($bytes / 1048576, $decimal_places), + 'G' => number_format($bytes / 1073741824, $decimal_places) + ); + return $formulas[$to] ?? 0; + } + + /** + * Return a pretty size based on bytes + * + * @param int $bytes + * @param int $precision + * @return string + */ + public static function prettySize($bytes, $precision = 2) + { + $units = array('B', 'KB', 'MB', 'GB', 'TB'); + + $bytes = max($bytes, 0); + $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); + $pow = min($pow, count($units) - 1); + + // Uncomment one of the following alternatives + $bytes /= 1024 ** $pow; + // $bytes /= (1 << (10 * $pow)); + + return round($bytes, $precision) . ' ' . $units[$pow]; + } + + /** + * Parse a readable file size and return a value in bytes + * + * @param string|int|float $size + * @return int + */ + public static function parseSize($size) + { + $unit = preg_replace('/[^bkmgtpezy]/i', '', $size); + $size = (float)preg_replace('/[^0-9\.]/', '', $size); + + if ($unit) { + $size *= 1024 ** stripos('bkmgtpezy', $unit[0]); + } + + return (int)abs(round($size)); + } + + /** + * Multibyte-safe Parse URL function + * + * @param string $url + * @return array + * @throws InvalidArgumentException + */ + public static function multibyteParseUrl($url) + { + $enc_url = preg_replace_callback( + '%[^:/@?&=#]+%usD', + static function ($matches) { + return urlencode($matches[0]); + }, + $url + ); + + $parts = parse_url($enc_url); + + if ($parts === false) { + $parts = []; + } + + foreach ($parts as $name => $value) { + $parts[$name] = urldecode($value); + } + + return $parts; + } + + /** + * Process a string as markdown + * + * @param string $string + * @param bool $block Block or Line processing + * @param PageInterface|null $page + * @return string + * @throws Exception + */ + public static function processMarkdown($string, $block = true, $page = null) + { + $grav = Grav::instance(); + $page = $page ?? $grav['page'] ?? null; + $defaults = [ + 'markdown' => $grav['config']->get('system.pages.markdown', []), + 'images' => $grav['config']->get('system.images', []) + ]; + $extra = $defaults['markdown']['extra'] ?? false; + + $excerpts = new Excerpts($page, $defaults); + + // Initialize the preferred variant of Parsedown + if ($extra) { + $parsedown = new ParsedownExtra($excerpts); + } else { + $parsedown = new Parsedown($excerpts); + } + + if ($block) { + $string = $parsedown->text((string) $string); + } else { + $string = $parsedown->line((string) $string); + } + + return $string; + } + + public static function toAscii(String $string): String + { + return strtr(utf8_decode($string), + utf8_decode( + 'ŠŒŽšœžŸ¥µÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿ'), + 'SOZsozYYuAAAAAAACEEEEIIIIDNOOOOOOUUUUYsaaaaaaaceeeeiiiionoooooouuuuyy'); + } + + /** + * Find the subnet of an ip with CIDR prefix size + * + * @param string $ip + * @param int $prefix + * @return string + */ + public static function getSubnet($ip, $prefix = 64) + { + if (!filter_var($ip, FILTER_VALIDATE_IP)) { + return $ip; + } + + // Packed representation of IP + $ip = (string)inet_pton($ip); + + // Maximum netmask length = same as packed address + $len = 8 * strlen($ip); + if ($prefix > $len) { + $prefix = $len; + } + + $mask = str_repeat('f', $prefix >> 2); + + switch ($prefix & 3) { + case 3: + $mask .= 'e'; + break; + case 2: + $mask .= 'c'; + break; + case 1: + $mask .= '8'; + break; + } + $mask = str_pad($mask, $len >> 2, '0'); + + // Packed representation of netmask + $mask = pack('H*', $mask); + // Bitwise - Take all bits that are both 1 to generate subnet + $subnet = inet_ntop($ip & $mask); + + return $subnet; + } + + /** + * Wrapper to ensure html, htm in the front of the supported page types + * + * @param array|null $defaults + * @return array + */ + public static function getSupportPageTypes(array $defaults = null) + { + $types = Grav::instance()['config']->get('system.pages.types', $defaults); + if (!is_array($types)) { + return []; + } + + // remove html/htm + $types = static::arrayRemoveValue($types, ['html', 'htm']); + + // put them back at the front + $types = array_merge(['html', 'htm'], $types); + + return $types; + } + + /** + * @param string|array|Closure $name + * @return bool + */ + public static function isDangerousFunction($name): bool + { + static $commandExecutionFunctions = [ + 'exec', + 'passthru', + 'system', + 'shell_exec', + 'popen', + 'proc_open', + 'pcntl_exec', + ]; + + static $codeExecutionFunctions = [ + 'assert', + 'preg_replace', + 'create_function', + 'include', + 'include_once', + 'require', + 'require_once' + ]; + + static $callbackFunctions = [ + 'ob_start' => 0, + 'array_diff_uassoc' => -1, + 'array_diff_ukey' => -1, + 'array_filter' => 1, + 'array_intersect_uassoc' => -1, + 'array_intersect_ukey' => -1, + 'array_map' => 0, + 'array_reduce' => 1, + 'array_udiff_assoc' => -1, + 'array_udiff_uassoc' => [-1, -2], + 'array_udiff' => -1, + 'array_uintersect_assoc' => -1, + 'array_uintersect_uassoc' => [-1, -2], + 'array_uintersect' => -1, + 'array_walk_recursive' => 1, + 'array_walk' => 1, + 'assert_options' => 1, + 'uasort' => 1, + 'uksort' => 1, + 'usort' => 1, + 'preg_replace_callback' => 1, + 'spl_autoload_register' => 0, + 'iterator_apply' => 1, + 'call_user_func' => 0, + 'call_user_func_array' => 0, + 'register_shutdown_function' => 0, + 'register_tick_function' => 0, + 'set_error_handler' => 0, + 'set_exception_handler' => 0, + 'session_set_save_handler' => [0, 1, 2, 3, 4, 5], + 'sqlite_create_aggregate' => [2, 3], + 'sqlite_create_function' => 2, + ]; + + static $informationDiscosureFunctions = [ + 'phpinfo', + 'posix_mkfifo', + 'posix_getlogin', + 'posix_ttyname', + 'getenv', + 'get_current_user', + 'proc_get_status', + 'get_cfg_var', + 'disk_free_space', + 'disk_total_space', + 'diskfreespace', + 'getcwd', + 'getlastmo', + 'getmygid', + 'getmyinode', + 'getmypid', + 'getmyuid' + ]; + + static $otherFunctions = [ + 'extract', + 'parse_str', + 'putenv', + 'ini_set', + 'mail', + 'header', + 'proc_nice', + 'proc_terminate', + 'proc_close', + 'pfsockopen', + 'fsockopen', + 'apache_child_terminate', + 'posix_kill', + 'posix_mkfifo', + 'posix_setpgid', + 'posix_setsid', + 'posix_setuid', + 'unserialize', + 'ini_alter', + 'simplexml_load_file', + 'simplexml_load_string', + 'forward_static_call', + 'forward_static_call_array', + ]; + + if (is_string($name)) { + $name = strtolower($name); + } + + if ($name instanceof \Closure) { + return false; + } + + if (is_array($name) || strpos($name, ":") !== false) { + return true; + } + + if (strpos($name, "\\") !== false) { + return true; + } + + if (in_array($name, $commandExecutionFunctions)) { + return true; + } + + if (in_array($name, $codeExecutionFunctions)) { + return true; + } + + if (isset($callbackFunctions[$name])) { + return true; + } + + if (in_array($name, $informationDiscosureFunctions)) { + return true; + } + + if (in_array($name, $otherFunctions)) { + return true; + } + + return static::isFilesystemFunction($name); + } + + /** + * @param string $name + * @return bool + */ + public static function isFilesystemFunction(string $name): bool + { + static $fileWriteFunctions = [ + 'fopen', + 'tmpfile', + 'bzopen', + 'gzopen', + // write to filesystem (partially in combination with reading) + 'chgrp', + 'chmod', + 'chown', + 'copy', + 'file_put_contents', + 'lchgrp', + 'lchown', + 'link', + 'mkdir', + 'move_uploaded_file', + 'rename', + 'rmdir', + 'symlink', + 'tempnam', + 'touch', + 'unlink', + 'imagepng', + 'imagewbmp', + 'image2wbmp', + 'imagejpeg', + 'imagexbm', + 'imagegif', + 'imagegd', + 'imagegd2', + 'iptcembed', + 'ftp_get', + 'ftp_nb_get', + ]; + + static $fileContentFunctions = [ + 'file_get_contents', + 'file', + 'filegroup', + 'fileinode', + 'fileowner', + 'fileperms', + 'glob', + 'is_executable', + 'is_uploaded_file', + 'parse_ini_file', + 'readfile', + 'readlink', + 'realpath', + 'gzfile', + 'readgzfile', + 'stat', + 'imagecreatefromgif', + 'imagecreatefromjpeg', + 'imagecreatefrompng', + 'imagecreatefromwbmp', + 'imagecreatefromxbm', + 'imagecreatefromxpm', + 'ftp_put', + 'ftp_nb_put', + 'hash_update_file', + 'highlight_file', + 'show_source', + 'php_strip_whitespace', + ]; + + static $filesystemFunctions = [ + // read from filesystem + 'file_exists', + 'fileatime', + 'filectime', + 'filemtime', + 'filesize', + 'filetype', + 'is_dir', + 'is_file', + 'is_link', + 'is_readable', + 'is_writable', + 'is_writeable', + 'linkinfo', + 'lstat', + //'pathinfo', + 'getimagesize', + 'exif_read_data', + 'read_exif_data', + 'exif_thumbnail', + 'exif_imagetype', + 'hash_file', + 'hash_hmac_file', + 'md5_file', + 'sha1_file', + 'get_meta_tags', + ]; + + if (in_array($name, $fileWriteFunctions)) { + return true; + } + + if (in_array($name, $fileContentFunctions)) { + return true; + } + + if (in_array($name, $filesystemFunctions)) { + return true; + } + + return false; + } +} diff --git a/system/src/Grav/Common/Yaml.php b/system/src/Grav/Common/Yaml.php new file mode 100644 index 0000000..5364db2 --- /dev/null +++ b/system/src/Grav/Common/Yaml.php @@ -0,0 +1,65 @@ +decode($data); + } + + /** + * @param array $data + * @param int|null $inline + * @param int|null $indent + * @return string + */ + public static function dump($data, $inline = null, $indent = null) + { + if (null === static::$yaml) { + static::init(); + } + + return static::$yaml->encode($data, $inline, $indent); + } + + /** + * @return void + */ + protected static function init() + { + $config = [ + 'inline' => 5, + 'indent' => 2, + 'native' => true, + 'compat' => true + ]; + + static::$yaml = new YamlFormatter($config); + } +} diff --git a/system/src/Grav/Console/Application/Application.php b/system/src/Grav/Console/Application/Application.php new file mode 100644 index 0000000..aefeee4 --- /dev/null +++ b/system/src/Grav/Console/Application/Application.php @@ -0,0 +1,138 @@ +addListener(ConsoleEvents::COMMAND, [$this, 'prepareEnvironment']); + + $this->setDispatcher($dispatcher); + } + + /** + * @param InputInterface $input + * @return string|null + */ + public function getCommandName(InputInterface $input): ?string + { + if ($input->hasParameterOption('--env', true)) { + $this->environment = $input->getParameterOption('--env'); + } + if ($input->hasParameterOption('--lang', true)) { + $this->language = $input->getParameterOption('--lang'); + } + + $this->init(); + + return parent::getCommandName($input); + } + + /** + * @param ConsoleCommandEvent $event + * @return void + */ + public function prepareEnvironment(ConsoleCommandEvent $event): void + { + } + + /** + * @return void + */ + protected function init(): void + { + if ($this->initialized) { + return; + } + + $this->initialized = true; + + $grav = Grav::instance(); + $grav->setup($this->environment); + } + + /** + * Add global --env and --lang options. + * + * @return InputDefinition + */ + protected function getDefaultInputDefinition(): InputDefinition + { + $inputDefinition = parent::getDefaultInputDefinition(); + $inputDefinition->addOption( + new InputOption( + '--env', + '', + InputOption::VALUE_OPTIONAL, + 'Use environment configuration (defaults to localhost)' + ) + ); + $inputDefinition->addOption( + new InputOption( + '--lang', + '', + InputOption::VALUE_OPTIONAL, + 'Language to be used (defaults to en)' + ) + ); + + return $inputDefinition; + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @return void + */ + protected function configureIO(InputInterface $input, OutputInterface $output) + { + $formatter = $output->getFormatter(); + $formatter->setStyle('normal', new OutputFormatterStyle('white')); + $formatter->setStyle('yellow', new OutputFormatterStyle('yellow', null, ['bold'])); + $formatter->setStyle('red', new OutputFormatterStyle('red', null, ['bold'])); + $formatter->setStyle('cyan', new OutputFormatterStyle('cyan', null, ['bold'])); + $formatter->setStyle('green', new OutputFormatterStyle('green', null, ['bold'])); + $formatter->setStyle('magenta', new OutputFormatterStyle('magenta', null, ['bold'])); + $formatter->setStyle('white', new OutputFormatterStyle('white', null, ['bold'])); + + parent::configureIO($input, $output); + } +} diff --git a/system/src/Grav/Console/Application/CommandLoader/PluginCommandLoader.php b/system/src/Grav/Console/Application/CommandLoader/PluginCommandLoader.php new file mode 100644 index 0000000..05b4097 --- /dev/null +++ b/system/src/Grav/Console/Application/CommandLoader/PluginCommandLoader.php @@ -0,0 +1,103 @@ +commands = []; + + try { + $path = "plugins://{$name}/cli"; + $pattern = '([A-Z]\w+Command\.php)'; + + $commands = is_dir($path) ? Folder::all($path, ['compare' => 'Filename', 'pattern' => '/' . $pattern . '$/usm', 'levels' => 1]) : []; + } catch (RuntimeException $e) { + throw new RuntimeException("Failed to load console commands for plugin {$name}"); + } + + $grav = Grav::instance(); + + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + foreach ($commands as $command_path) { + $full_path = $locator->findResource("plugins://{$name}/cli/{$command_path}"); + require_once $full_path; + + $command_class = 'Grav\Plugin\Console\\' . preg_replace('/.php$/', '', $command_path); + if (class_exists($command_class)) { + $command = new $command_class(); + if ($command instanceof Command) { + $this->commands[$command->getName()] = $command; + + // If the command has an alias, add that as a possible command name. + $aliases = $this->commands[$command->getName()]->getAliases(); + if (isset($aliases)) { + foreach ($aliases as $alias) { + $this->commands[$alias] = $command; + } + } + } + } + } + } + + /** + * @param string $name + * @return Command + */ + public function get($name): Command + { + $command = $this->commands[$name] ?? null; + if (null === $command) { + throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name)); + } + + return $command; + } + + /** + * @param string $name + * @return bool + */ + public function has($name): bool + { + return isset($this->commands[$name]); + } + + /** + * @return string[] + */ + public function getNames(): array + { + return array_keys($this->commands); + } +} diff --git a/system/src/Grav/Console/Application/GpmApplication.php b/system/src/Grav/Console/Application/GpmApplication.php new file mode 100644 index 0000000..61e73d2 --- /dev/null +++ b/system/src/Grav/Console/Application/GpmApplication.php @@ -0,0 +1,42 @@ +addCommands([ + new IndexCommand(), + new VersionCommand(), + new InfoCommand(), + new InstallCommand(), + new UninstallCommand(), + new UpdateCommand(), + new SelfupgradeCommand(), + new DirectInstallCommand(), + ]); + } +} diff --git a/system/src/Grav/Console/Application/GravApplication.php b/system/src/Grav/Console/Application/GravApplication.php new file mode 100644 index 0000000..81e320d --- /dev/null +++ b/system/src/Grav/Console/Application/GravApplication.php @@ -0,0 +1,52 @@ +addCommands([ + new InstallCommand(), + new ComposerCommand(), + new SandboxCommand(), + new CleanCommand(), + new ClearCacheCommand(), + new BackupCommand(), + new NewProjectCommand(), + new SchedulerCommand(), + new SecurityCommand(), + new LogViewerCommand(), + new YamlLinterCommand(), + new ServerCommand(), + new PageSystemValidatorCommand(), + ]); + } +} diff --git a/system/src/Grav/Console/Application/PluginApplication.php b/system/src/Grav/Console/Application/PluginApplication.php new file mode 100644 index 0000000..fc28386 --- /dev/null +++ b/system/src/Grav/Console/Application/PluginApplication.php @@ -0,0 +1,116 @@ +addCommands([ + new PluginListCommand(), + ]); + } + + /** + * @param string $pluginName + * @return void + */ + public function setPluginName(string $pluginName): void + { + $this->pluginName = $pluginName; + } + + /** + * @return string + */ + public function getPluginName(): string + { + return $this->pluginName; + } + + /** + * @param InputInterface|null $input + * @param OutputInterface|null $output + * @return int + * @throws Throwable + */ + public function run(InputInterface $input = null, OutputInterface $output = null): int + { + if (null === $input) { + $argv = $_SERVER['argv'] ?? []; + + $bin = array_shift($argv); + $this->pluginName = array_shift($argv); + $argv = array_merge([$bin], $argv); + + $input = new ArgvInput($argv); + } + + return parent::run($input, $output); + } + + /** + * @return void + */ + protected function init(): void + { + if ($this->initialized) { + return; + } + + parent::init(); + + if (null === $this->pluginName) { + $this->setDefaultCommand('plugins:list'); + + return; + } + + $grav = Grav::instance(); + $grav->initializeCli(); + + /** @var Plugins $plugins */ + $plugins = $grav['plugins']; + + $plugin = $this->pluginName ? $plugins::get($this->pluginName) : null; + if (null === $plugin) { + throw new NamespaceNotFoundException("Plugin \"{$this->pluginName}\" is not installed."); + } + if (!$plugin->enabled) { + throw new NamespaceNotFoundException("Plugin \"{$this->pluginName}\" is not enabled."); + } + + $this->setCommandLoader(new PluginCommandLoader($this->pluginName)); + } +} diff --git a/system/src/Grav/Console/Cli/BackupCommand.php b/system/src/Grav/Console/Cli/BackupCommand.php new file mode 100644 index 0000000..fe3c894 --- /dev/null +++ b/system/src/Grav/Console/Cli/BackupCommand.php @@ -0,0 +1,138 @@ +setName('backup') + ->addArgument( + 'id', + InputArgument::OPTIONAL, + 'The ID of the backup profile to perform without prompting' + ) + ->setDescription('Creates a backup of the Grav instance') + ->setHelp('The backup creates a zipped backup.'); + + $this->source = getcwd(); + } + + /** + * @return int + */ + protected function serve(): int + { + $this->initializeGrav(); + + $input = $this->getInput(); + $io = $this->getIO(); + + $io->title('Grav Backup'); + + if (!class_exists(ZipArchive::class)) { + $io->error('php-zip extension needs to be enabled!'); + return 1; + } + + ProgressBar::setFormatDefinition('zip', 'Archiving %current% files [%bar%] %percent:3s%% %elapsed:6s% %message%'); + + $this->progress = new ProgressBar($this->output, 100); + $this->progress->setFormat('zip'); + + + /** @var Backups $backups */ + $backups = Grav::instance()['backups']; + $backups_list = $backups::getBackupProfiles(); + $backups_names = $backups->getBackupNames(); + + $id = null; + + $inline_id = $input->getArgument('id'); + if (null !== $inline_id && is_numeric($inline_id)) { + $id = $inline_id; + } + + if (null === $id) { + if (count($backups_list) > 1) { + $question = new ChoiceQuestion( + 'Choose a backup?', + $backups_names, + 0 + ); + $question->setErrorMessage('Option %s is invalid.'); + $backup_name = $io->askQuestion($question); + $id = array_search($backup_name, $backups_names, true); + + $io->newLine(); + $io->note('Selected backup: ' . $backup_name); + } else { + $id = 0; + } + } + + $backup = $backups::backup($id, function($args) { $this->outputProgress($args); }); + + $io->newline(2); + $io->success('Backup Successfully Created: ' . $backup); + + return 0; + } + + /** + * @param array $args + * @return void + */ + public function outputProgress(array $args): void + { + switch ($args['type']) { + case 'count': + $steps = $args['steps']; + $freq = (int)($steps > 100 ? round($steps / 100) : $steps); + $this->progress->setMaxSteps($steps); + $this->progress->setRedrawFrequency($freq); + $this->progress->setMessage('Adding files...'); + break; + case 'message': + $this->progress->setMessage($args['message']); + $this->progress->display(); + break; + case 'progress': + if (isset($args['complete']) && $args['complete']) { + $this->progress->finish(); + } else { + $this->progress->advance(); + } + break; + } + } +} diff --git a/system/src/Grav/Console/Cli/CleanCommand.php b/system/src/Grav/Console/Cli/CleanCommand.php new file mode 100644 index 0000000..1418a63 --- /dev/null +++ b/system/src/Grav/Console/Cli/CleanCommand.php @@ -0,0 +1,411 @@ +setName('clean') + ->setDescription('Handles cleaning chores for Grav distribution') + ->setHelp('The clean clean extraneous folders and data'); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @return int + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->setupConsole($input, $output); + + return $this->cleanPaths() ? 0 : 1; + } + + /** + * @return bool + */ + private function cleanPaths(): bool + { + $success = true; + + $this->io->writeln(''); + $this->io->writeln('DELETING'); + $anything = false; + foreach ($this->paths_to_remove as $path) { + $path = GRAV_ROOT . DS . $path; + try { + if (is_dir($path) && Folder::delete($path)) { + $anything = true; + $this->io->writeln('dir: ' . $path); + } elseif (is_file($path) && @unlink($path)) { + $anything = true; + $this->io->writeln('file: ' . $path); + } + } catch (\Exception $e) { + $success = false; + $this->io->error(sprintf('Failed to delete %s: %s', $path, $e->getMessage())); + } + } + if (!$anything) { + $this->io->writeln(''); + $this->io->writeln('Nothing to clean...'); + } + + return $success; + } + + /** + * Set colors style definition for the formatter. + * + * @param InputInterface $input + * @param OutputInterface $output + * @return void + */ + public function setupConsole(InputInterface $input, OutputInterface $output): void + { + $this->input = $input; + $this->io = new SymfonyStyle($input, $output); + + $this->io->getFormatter()->setStyle('normal', new OutputFormatterStyle('white')); + $this->io->getFormatter()->setStyle('yellow', new OutputFormatterStyle('yellow', null, ['bold'])); + $this->io->getFormatter()->setStyle('red', new OutputFormatterStyle('red', null, ['bold'])); + $this->io->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan', null, ['bold'])); + $this->io->getFormatter()->setStyle('green', new OutputFormatterStyle('green', null, ['bold'])); + $this->io->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta', null, ['bold'])); + $this->io->getFormatter()->setStyle('white', new OutputFormatterStyle('white', null, ['bold'])); + } +} diff --git a/system/src/Grav/Console/Cli/ClearCacheCommand.php b/system/src/Grav/Console/Cli/ClearCacheCommand.php new file mode 100644 index 0000000..98f693b --- /dev/null +++ b/system/src/Grav/Console/Cli/ClearCacheCommand.php @@ -0,0 +1,104 @@ +setName('cache') + ->setAliases(['clearcache', 'cache-clear']) + ->setDescription('Clears Grav cache') + ->addOption('invalidate', null, InputOption::VALUE_NONE, 'Invalidate cache, but do not remove any files') + ->addOption('purge', null, InputOption::VALUE_NONE, 'If set purge old caches') + ->addOption('all', null, InputOption::VALUE_NONE, 'If set will remove all including compiled, twig, doctrine caches') + ->addOption('assets-only', null, InputOption::VALUE_NONE, 'If set will remove only assets/*') + ->addOption('images-only', null, InputOption::VALUE_NONE, 'If set will remove only images/*') + ->addOption('cache-only', null, InputOption::VALUE_NONE, 'If set will remove only cache/*') + ->addOption('tmp-only', null, InputOption::VALUE_NONE, 'If set will remove only tmp/*') + + ->setHelp('The cache command allows you to interact with Grav cache'); + } + + /** + * @return int + */ + protected function serve(): int + { + // Old versions of Grav called this command after grav upgrade. + // We need make this command to work with older GravCommand instance: + if (!method_exists($this, 'initializePlugins')) { + Cache::clearCache('all'); + + return 0; + } + + $this->initializePlugins(); + $this->cleanPaths(); + + return 0; + } + + /** + * loops over the array of paths and deletes the files/folders + * + * @return void + */ + private function cleanPaths(): void + { + $input = $this->getInput(); + $io = $this->getIO(); + + $io->newLine(); + + if ($input->getOption('purge')) { + $io->writeln('Purging old cache'); + $io->newLine(); + + $msg = Cache::purgeJob(); + $io->writeln($msg); + } else { + $io->writeln('Clearing cache'); + $io->newLine(); + + if ($input->getOption('all')) { + $remove = 'all'; + } elseif ($input->getOption('assets-only')) { + $remove = 'assets-only'; + } elseif ($input->getOption('images-only')) { + $remove = 'images-only'; + } elseif ($input->getOption('cache-only')) { + $remove = 'cache-only'; + } elseif ($input->getOption('tmp-only')) { + $remove = 'tmp-only'; + } elseif ($input->getOption('invalidate')) { + $remove = 'invalidate'; + } else { + $remove = 'standard'; + } + + foreach (Cache::clearCache($remove) as $result) { + $io->writeln($result); + } + } + } +} diff --git a/system/src/Grav/Console/Cli/ComposerCommand.php b/system/src/Grav/Console/Cli/ComposerCommand.php new file mode 100644 index 0000000..8617698 --- /dev/null +++ b/system/src/Grav/Console/Cli/ComposerCommand.php @@ -0,0 +1,64 @@ +setName('composer') + ->addOption( + 'install', + 'i', + InputOption::VALUE_NONE, + 'install the dependencies' + ) + ->addOption( + 'update', + 'u', + InputOption::VALUE_NONE, + 'update the dependencies' + ) + ->setDescription('Updates the composer vendor dependencies needed by Grav.') + ->setHelp('The composer command updates the composer vendor dependencies needed by Grav'); + } + + /** + * @return int + */ + protected function serve(): int + { + $input = $this->getInput(); + $io = $this->getIO(); + + $action = $input->getOption('install') ? 'install' : ($input->getOption('update') ? 'update' : 'install'); + + if ($input->getOption('install')) { + $action = 'install'; + } + + // Updates composer first + $io->writeln("\nInstalling vendor dependencies"); + $io->writeln($this->composerUpdate(GRAV_ROOT, $action)); + + return 0; + } +} diff --git a/system/src/Grav/Console/Cli/InstallCommand.php b/system/src/Grav/Console/Cli/InstallCommand.php new file mode 100644 index 0000000..0f9adb2 --- /dev/null +++ b/system/src/Grav/Console/Cli/InstallCommand.php @@ -0,0 +1,302 @@ +setName('install') + ->addOption( + 'symlink', + 's', + InputOption::VALUE_NONE, + 'Symlink the required bits' + ) + ->addOption( + 'plugin', + 'p', + InputOption::VALUE_REQUIRED, + 'Install plugin (symlink)' + ) + ->addOption( + 'theme', + 't', + InputOption::VALUE_REQUIRED, + 'Install theme (symlink)' + ) + ->addArgument( + 'destination', + InputArgument::OPTIONAL, + 'Where to install the required bits (default to current project)' + ) + ->setDescription('Installs the dependencies needed by Grav. Optionally can create symbolic links') + ->setHelp('The install command installs the dependencies needed by Grav. Optionally can create symbolic links'); + } + + /** + * @return int + */ + protected function serve(): int + { + $input = $this->getInput(); + $io = $this->getIO(); + + $dependencies_file = '.dependencies'; + $this->destination = $input->getArgument('destination') ?: GRAV_WEBROOT; + + // fix trailing slash + $this->destination = rtrim($this->destination, DS) . DS; + $this->user_path = $this->destination . GRAV_USER_PATH . DS; + if ($local_config_file = $this->loadLocalConfig()) { + $io->writeln('Read local config from ' . $local_config_file . ''); + } + + // Look for dependencies file in ROOT and USER dir + if (file_exists($this->user_path . $dependencies_file)) { + $file = YamlFile::instance($this->user_path . $dependencies_file); + } elseif (file_exists($this->destination . $dependencies_file)) { + $file = YamlFile::instance($this->destination . $dependencies_file); + } else { + $io->writeln('ERROR Missing .dependencies file in user/ folder'); + if ($input->getArgument('destination')) { + $io->writeln('HINT Are you trying to install a plugin or a theme? Make sure you use bin/gpm install , not bin/grav install. This command is only used to install Grav skeletons.'); + } else { + $io->writeln('HINT Are you trying to install Grav? Grav is already installed. You need to run this command only if you download a skeleton from GitHub directly.'); + } + + return 1; + } + + $this->config = $file->content(); + $file->free(); + + // If no config, fail. + if (!$this->config) { + $io->writeln('ERROR invalid YAML in ' . $dependencies_file); + + return 1; + } + + $plugin = $input->getOption('plugin'); + $theme = $input->getOption('theme'); + $name = $plugin ?? $theme; + $symlink = $name || $input->getOption('symlink'); + + if (!$symlink) { + // Updates composer first + $io->writeln("\nInstalling vendor dependencies"); + $io->writeln($this->composerUpdate(GRAV_ROOT, 'install')); + + $error = $this->gitclone(); + } else { + $type = $name ? ($plugin ? 'plugin' : 'theme') : null; + + $error = $this->symlink($name, $type); + } + + return $error; + } + + /** + * Clones from Git + * + * @return int + */ + private function gitclone(): int + { + $io = $this->getIO(); + + $io->newLine(); + $io->writeln('Cloning Bits'); + $io->writeln('============'); + $io->newLine(); + + $error = 0; + $this->destination = rtrim($this->destination, DS); + foreach ($this->config['git'] as $repo => $data) { + $path = $this->destination . DS . $data['path']; + if (!file_exists($path)) { + exec('cd ' . escapeshellarg($this->destination) . ' && git clone -b ' . $data['branch'] . ' --depth 1 ' . $data['url'] . ' ' . $data['path'], $output, $return); + + if (!$return) { + $io->writeln('SUCCESS cloned ' . $data['url'] . ' -> ' . $path . ''); + } else { + $io->writeln('ERROR cloning ' . $data['url']); + $error = 1; + } + + $io->newLine(); + } else { + $io->writeln('' . $path . ' already exists, skipping...'); + $io->newLine(); + } + } + + return $error; + } + + /** + * Symlinks + * + * @param string|null $name + * @param string|null $type + * @return int + */ + private function symlink(string $name = null, string $type = null): int + { + $io = $this->getIO(); + + $io->newLine(); + $io->writeln('Symlinking Bits'); + $io->writeln('==============='); + $io->newLine(); + + if (!$this->local_config) { + $io->writeln('No local configuration available, aborting...'); + $io->newLine(); + + return 1; + } + + $error = 0; + $this->destination = rtrim($this->destination, DS); + + if ($name) { + $src = "grav-{$type}-{$name}"; + $links = [ + $name => [ + 'scm' => 'github', // TODO: make configurable + 'src' => $src, + 'path' => "user/{$type}s/{$name}" + ] + ]; + } else { + $links = $this->config['links']; + } + + foreach ($links as $name => $data) { + $scm = $data['scm'] ?? null; + $src = $data['src'] ?? null; + $path = $data['path'] ?? null; + if (!isset($scm, $src, $path)) { + $io->writeln("Dependency '$name' has broken configuration, skipping..."); + $io->newLine(); + $error = 1; + + continue; + } + + $locations = (array) $this->local_config["{$scm}_repos"]; + $to = $this->destination . DS . $path; + + $from = null; + foreach ($locations as $location) { + $test = rtrim($location, '\\/') . DS . $src; + if (file_exists($test)) { + $from = $test; + continue; + } + } + + if (is_link($to) && !realpath($to)) { + $io->writeln('Removed broken symlink '. $path .''); + unlink($to); + } + if (null === $from) { + $io->writeln('source for ' . $src . ' does not exists, skipping...'); + $io->newLine(); + $error = 1; + } elseif (!file_exists($to)) { + $error = $this->addSymlinks($from, $to, ['name' => $name, 'src' => $src, 'path' => $path]); + $io->newLine(); + } else { + $io->writeln('destination: ' . $path . ' already exists, skipping...'); + $io->newLine(); + } + } + + return $error; + } + + private function addSymlinks(string $from, string $to, array $options): int + { + $io = $this->getIO(); + + $hebe = $this->readHebe($from); + if (null === $hebe) { + symlink($from, $to); + + $io->writeln('SUCCESS symlinked ' . $options['src'] . ' -> ' . $options['path'] . ''); + } else { + $to = GRAV_ROOT; + $name = $options['name']; + $io->writeln("Processing {$name}"); + foreach ($hebe as $section => $symlinks) { + foreach ($symlinks as $symlink) { + $src = trim($symlink['source'], '/'); + $dst = trim($symlink['destination'], '/'); + $s = "{$from}/{$src}"; + $d = "{$to}/{$dst}"; + + if (is_link($d) && !realpath($d)) { + unlink($d); + $io->writeln(' Removed broken symlink '. $dst .''); + } + if (!file_exists($d)) { + symlink($s, $d); + $io->writeln(' symlinked ' . $src . ' -> ' . $dst . ''); + } + } + } + $io->writeln('SUCCESS'); + } + + return 0; + } + + private function readHebe(string $folder): ?array + { + $filename = "{$folder}/hebe.json"; + if (!is_file($filename)) { + return null; + } + + $formatter = new JsonFormatter(); + $file = new JsonFile($filename, $formatter); + $hebe = $file->load(); + $paths = $hebe['platforms']['grav']['nodes'] ?? null; + + return is_array($paths) ? $paths : null; + } +} diff --git a/system/src/Grav/Console/Cli/LogViewerCommand.php b/system/src/Grav/Console/Cli/LogViewerCommand.php new file mode 100644 index 0000000..62ee79c --- /dev/null +++ b/system/src/Grav/Console/Cli/LogViewerCommand.php @@ -0,0 +1,96 @@ +setName('logviewer') + ->addOption( + 'file', + 'f', + InputOption::VALUE_OPTIONAL, + 'custom log file location (default = grav.log)' + ) + ->addOption( + 'lines', + 'l', + InputOption::VALUE_OPTIONAL, + 'number of lines (default = 10)' + ) + ->setDescription('Display the last few entries of Grav log') + ->setHelp('Display the last few entries of Grav log'); + } + + /** + * @return int + */ + protected function serve(): int + { + $input = $this->getInput(); + $io = $this->getIO(); + + $file = $input->getOption('file') ?? 'grav.log'; + $lines = $input->getOption('lines') ?? 20; + $verbose = $input->getOption('verbose') ?? false; + + $io->title('Log Viewer'); + + $io->writeln(sprintf('viewing last %s entries in %s', $lines, $file)); + $io->newLine(); + + $viewer = new LogViewer(); + + $grav = Grav::instance(); + + $logfile = $grav['locator']->findResource('log://' . $file); + if (!$logfile) { + $io->error('cannot find the log file: logs/' . $file); + + return 1; + } + + $rows = $viewer->objectTail($logfile, $lines, true); + foreach ($rows as $log) { + $date = $log['date']; + $level_color = LogViewer::levelColor($log['level']); + + if ($date instanceof DateTime) { + $output = "{$log['date']->format('Y-m-d h:i:s')} [<{$level_color}>{$log['level']}]"; + if ($log['trace'] && $verbose) { + $output .= " {$log['message']}\n"; + foreach ((array) $log['trace'] as $index => $tracerow) { + $output .= "{$index}{$tracerow}\n"; + } + } else { + $output .= " {$log['message']}"; + } + $io->writeln($output); + } + } + + return 0; + } +} diff --git a/system/src/Grav/Console/Cli/NewProjectCommand.php b/system/src/Grav/Console/Cli/NewProjectCommand.php new file mode 100644 index 0000000..b6a3c8b --- /dev/null +++ b/system/src/Grav/Console/Cli/NewProjectCommand.php @@ -0,0 +1,75 @@ +setName('new-project') + ->setAliases(['newproject']) + ->addArgument( + 'destination', + InputArgument::REQUIRED, + 'The destination directory of your new Grav project' + ) + ->addOption( + 'symlink', + 's', + InputOption::VALUE_NONE, + 'Symlink the required bits' + ) + ->setDescription('Creates a new Grav project with all the dependencies installed') + ->setHelp("The new-project command is a combination of the `setup` and `install` commands.\nCreates a new Grav instance and performs the installation of all the required dependencies."); + } + + /** + * @return int + */ + protected function serve(): int + { + $io = $this->getIO(); + + $sandboxCommand = $this->getApplication()->find('sandbox'); + $installCommand = $this->getApplication()->find('install'); + + $sandboxArguments = new ArrayInput([ + 'command' => 'sandbox', + 'destination' => $this->input->getArgument('destination'), + '-s' => $this->input->getOption('symlink') + ]); + + $installArguments = new ArrayInput([ + 'command' => 'install', + 'destination' => $this->input->getArgument('destination'), + '-s' => $this->input->getOption('symlink') + ]); + + $error = $sandboxCommand->run($sandboxArguments, $io); + if ($error === 0) { + $error = $installCommand->run($installArguments, $io); + } + + return $error; + } +} diff --git a/system/src/Grav/Console/Cli/PageSystemValidatorCommand.php b/system/src/Grav/Console/Cli/PageSystemValidatorCommand.php new file mode 100644 index 0000000..ce8ffdd --- /dev/null +++ b/system/src/Grav/Console/Cli/PageSystemValidatorCommand.php @@ -0,0 +1,299 @@ + [[]], + 'summary' => [[], [200], [200, true]], + 'content' => [[]], + 'getRawContent' => [[]], + 'rawMarkdown' => [[]], + 'value' => [['content'], ['route'], ['order'], ['ordering'], ['folder'], ['slug'], ['name'], /*['frontmatter'],*/ ['header.menu'], ['header.slug']], + 'title' => [[]], + 'menu' => [[]], + 'visible' => [[]], + 'published' => [[]], + 'publishDate' => [[]], + 'unpublishDate' => [[]], + 'process' => [[]], + 'slug' => [[]], + 'order' => [[]], + //'id' => [[]], + 'modified' => [[]], + 'lastModified' => [[]], + 'folder' => [[]], + 'date' => [[]], + 'dateformat' => [[]], + 'taxonomy' => [[]], + 'shouldProcess' => [['twig'], ['markdown']], + 'isPage' => [[]], + 'isDir' => [[]], + 'exists' => [[]], + + // Forms + 'forms' => [[]], + + // Routing + 'urlExtension' => [[]], + 'routable' => [[]], + 'link' => [[], [false], [true]], + 'permalink' => [[]], + 'canonical' => [[], [false], [true]], + 'url' => [[], [true], [true, true], [true, true, false], [false, false, true, false]], + 'route' => [[]], + 'rawRoute' => [[]], + 'routeAliases' => [[]], + 'routeCanonical' => [[]], + 'redirect' => [[]], + 'relativePagePath' => [[]], + 'path' => [[]], + //'folder' => [[]], + 'parent' => [[]], + 'topParent' => [[]], + 'currentPosition' => [[]], + 'active' => [[]], + 'activeChild' => [[]], + 'home' => [[]], + 'root' => [[]], + + // Translations + 'translatedLanguages' => [[], [false], [true]], + 'untranslatedLanguages' => [[], [false], [true]], + 'language' => [[]], + + // Legacy + 'raw' => [[]], + 'frontmatter' => [[]], + 'httpResponseCode' => [[]], + 'httpHeaders' => [[]], + 'blueprintName' => [[]], + 'name' => [[]], + 'childType' => [[]], + 'template' => [[]], + 'templateFormat' => [[]], + 'extension' => [[]], + 'expires' => [[]], + 'cacheControl' => [[]], + 'ssl' => [[]], + 'metadata' => [[]], + 'eTag' => [[]], + 'filePath' => [[]], + 'filePathClean' => [[]], + 'orderDir' => [[]], + 'orderBy' => [[]], + 'orderManual' => [[]], + 'maxCount' => [[]], + 'modular' => [[]], + 'modularTwig' => [[]], + //'children' => [[]], + 'isFirst' => [[]], + 'isLast' => [[]], + 'prevSibling' => [[]], + 'nextSibling' => [[]], + 'adjacentSibling' => [[]], + 'ancestor' => [[]], + //'inherited' => [[]], + //'inheritedField' => [[]], + 'find' => [['/']], + //'collection' => [[]], + //'evaluate' => [[]], + 'folderExists' => [[]], + //'getOriginal' => [[]], + //'getAction' => [[]], + ]; + + /** @var Grav */ + protected $grav; + + /** + * @return void + */ + protected function configure(): void + { + $this + ->setName('page-system-validator') + ->setDescription('Page validator can be used to compare site before/after update and when migrating to Flex Pages.') + ->addOption('record', 'r', InputOption::VALUE_NONE, 'Record results') + ->addOption('check', 'c', InputOption::VALUE_NONE, 'Compare site against previously recorded results') + ->setHelp('The page-system-validator command can be used to test the pages before and after upgrade'); + } + + /** + * @return int + */ + protected function serve(): int + { + $input = $this->getInput(); + $io = $this->getIO(); + + $this->setLanguage('en'); + $this->initializePages(); + + $io->newLine(); + + $this->grav = $grav = Grav::instance(); + + $grav->fireEvent('onPageInitialized', new Event(['page' => $grav['page']])); + + /** @var Config $config */ + $config = $grav['config']; + + if ($input->getOption('record')) { + $io->writeln('Pages: ' . $config->get('system.pages.type', 'page')); + + $io->writeln('Record tests'); + $io->newLine(); + + $results = $this->record(); + $file = $this->getFile('pages-old'); + $file->save($results); + + $io->writeln('Recorded tests to ' . $file->filename()); + } elseif ($input->getOption('check')) { + $io->writeln('Pages: ' . $config->get('system.pages.type', 'page')); + + $io->writeln('Run tests'); + $io->newLine(); + + $new = $this->record(); + $file = $this->getFile('pages-new'); + $file->save($new); + $io->writeln('Recorded tests to ' . $file->filename()); + + $file = $this->getFile('pages-old'); + $old = $file->content(); + + $results = $this->check($old, $new); + $file = $this->getFile('diff'); + $file->save($results); + $io->writeln('Recorded results to ' . $file->filename()); + } else { + $io->writeln('page-system-validator [-r|--record] [-c|--check]'); + } + $io->newLine(); + + return 0; + } + + /** + * @return array + */ + private function record(): array + { + $io = $this->getIO(); + + /** @var Pages $pages */ + $pages = $this->grav['pages']; + $all = $pages->all(); + + $results = []; + $results[''] = $this->recordRow($pages->root()); + foreach ($all as $path => $page) { + if (null === $page) { + $io->writeln('Error on page ' . $path . ''); + continue; + } + + $results[$page->rawRoute()] = $this->recordRow($page); + } + + return json_decode(json_encode($results), true); + } + + /** + * @param PageInterface $page + * @return array + */ + private function recordRow(PageInterface $page): array + { + $results = []; + + foreach ($this->tests as $method => $params) { + $params = $params ?: [[]]; + foreach ($params as $p) { + $result = $page->$method(...$p); + if (in_array($method, ['summary', 'content', 'getRawContent'], true)) { + $result = preg_replace('/name="(form-nonce|__unique_form_id__)" value="[^"]+"/', + 'name="\\1" value="DYNAMIC"', $result); + $result = preg_replace('`src=("|\'|")/images/./././././[^"]+\\1`', + 'src="\\1images/GENERATED\\1', $result); + $result = preg_replace('/\?\d{10}/', '?1234567890', $result); + } elseif ($method === 'httpHeaders' && isset($result['Expires'])) { + $result['Expires'] = 'Thu, 19 Sep 2019 13:10:24 GMT (REPLACED AS DYNAMIC)'; + } elseif ($result instanceof PageInterface) { + $result = $result->rawRoute(); + } elseif (is_object($result)) { + $result = json_decode(json_encode($result), true); + } + + $ps = []; + foreach ($p as $val) { + $ps[] = (string)var_export($val, true); + } + $pstr = implode(', ', $ps); + $call = "->{$method}({$pstr})"; + $results[$call] = $result; + } + } + + return $results; + } + + /** + * @param array $old + * @param array $new + * @return array + */ + private function check(array $old, array $new): array + { + $errors = []; + foreach ($old as $path => $page) { + if (!isset($new[$path])) { + $errors[$path] = 'PAGE REMOVED'; + continue; + } + foreach ($page as $method => $test) { + if (($new[$path][$method] ?? null) !== $test) { + $errors[$path][$method] = ['old' => $test, 'new' => $new[$path][$method]]; + } + } + } + + return $errors; + } + + /** + * @param string $name + * @return CompiledYamlFile + */ + private function getFile(string $name): CompiledYamlFile + { + return CompiledYamlFile::instance('cache://tests/' . $name . '.yaml'); + } +} diff --git a/system/src/Grav/Console/Cli/SandboxCommand.php b/system/src/Grav/Console/Cli/SandboxCommand.php new file mode 100644 index 0000000..883388b --- /dev/null +++ b/system/src/Grav/Console/Cli/SandboxCommand.php @@ -0,0 +1,347 @@ + '/.gitignore', + '/.editorconfig' => '/.editorconfig', + '/CHANGELOG.md' => '/CHANGELOG.md', + '/LICENSE.txt' => '/LICENSE.txt', + '/README.md' => '/README.md', + '/CONTRIBUTING.md' => '/CONTRIBUTING.md', + '/index.php' => '/index.php', + '/composer.json' => '/composer.json', + '/bin' => '/bin', + '/system' => '/system', + '/vendor' => '/vendor', + '/webserver-configs' => '/webserver-configs', + ]; + + /** @var string */ + protected $source; + /** @var string */ + protected $destination; + + /** + * @return void + */ + protected function configure(): void + { + $this + ->setName('sandbox') + ->setDescription('Setup of a base Grav system in your webroot, good for development, playing around or starting fresh') + ->addArgument( + 'destination', + InputArgument::REQUIRED, + 'The destination directory to symlink into' + ) + ->addOption( + 'symlink', + 's', + InputOption::VALUE_NONE, + 'Symlink the base grav system' + ) + ->setHelp("The sandbox command help create a development environment that can optionally use symbolic links to link the core of grav to the git cloned repository.\nGood for development, playing around or starting fresh"); + + $source = getcwd(); + if ($source === false) { + throw new RuntimeException('Internal Error'); + } + $this->source = $source; + } + + /** + * @return int + */ + protected function serve(): int + { + $input = $this->getInput(); + + $this->destination = $input->getArgument('destination'); + + // Create Some core stuff if it doesn't exist + $error = $this->createDirectories(); + if ($error) { + return $error; + } + + // Copy files or create symlinks + $error = $input->getOption('symlink') ? $this->symlink() : $this->copy(); + if ($error) { + return $error; + } + + $error = $this->pages(); + if ($error) { + return $error; + } + + $error = $this->initFiles(); + if ($error) { + return $error; + } + + $error = $this->perms(); + if ($error) { + return $error; + } + + return 0; + } + + /** + * @return int + */ + private function createDirectories(): int + { + $io = $this->getIO(); + + $io->newLine(); + $io->writeln('Creating Directories'); + $dirs_created = false; + + if (!file_exists($this->destination)) { + Folder::create($this->destination); + } + + foreach ($this->directories as $dir) { + if (!file_exists($this->destination . $dir)) { + $dirs_created = true; + $io->writeln(' ' . $dir . ''); + Folder::create($this->destination . $dir); + } + } + + if (!$dirs_created) { + $io->writeln(' Directories already exist'); + } + + return 0; + } + + /** + * @return int + */ + private function copy(): int + { + $io = $this->getIO(); + + $io->newLine(); + $io->writeln('Copying Files'); + + + foreach ($this->mappings as $source => $target) { + if ((string)(int)$source === (string)$source) { + $source = $target; + } + + $from = $this->source . $source; + $to = $this->destination . $target; + + $io->writeln(' ' . $source . ' -> ' . $to); + @Folder::rcopy($from, $to); + } + + return 0; + } + + /** + * @return int + */ + private function symlink(): int + { + $io = $this->getIO(); + + $io->newLine(); + $io->writeln('Resetting Symbolic Links'); + + // Symlink also tests if using git. + if (is_dir($this->source . '/tests')) { + $this->mappings['/tests'] = '/tests'; + } + + foreach ($this->mappings as $source => $target) { + if ((string)(int)$source === (string)$source) { + $source = $target; + } + + $from = $this->source . $source; + $to = $this->destination . $target; + + $io->writeln(' ' . $source . ' -> ' . $to); + + if (is_dir($to)) { + @Folder::delete($to); + } else { + @unlink($to); + } + symlink($from, $to); + } + + return 0; + } + + /** + * @return int + */ + private function pages(): int + { + $io = $this->getIO(); + + $io->newLine(); + $io->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), ['..', '.']); + + if (count($pages_files) === 0) { + $destination = $this->source . '/user/pages'; + Folder::rcopy($destination, $pages_dir); + $io->writeln(' ' . $destination . ' -> Created'); + } + + return 0; + } + + /** + * @return int + */ + private function initFiles(): int + { + if (!$this->check()) { + return 1; + } + + $io = $this->getIO(); + $io->newLine(); + $io->writeln('File Initializing'); + $files_init = false; + + // Copy files if they do not exist + foreach ($this->files as $source => $target) { + if ((string)(int)$source === (string)$source) { + $source = $target; + } + + $from = $this->source . $source; + $to = $this->destination . $target; + + if (!file_exists($to)) { + $files_init = true; + copy($from, $to); + $io->writeln(' ' . $target . ' -> Created'); + } + } + + if (!$files_init) { + $io->writeln(' Files already exist'); + } + + return 0; + } + + /** + * @return int + */ + private function perms(): int + { + $io = $this->getIO(); + $io->newLine(); + $io->writeln('Permissions Initializing'); + + $dir_perms = 0755; + + $binaries = glob($this->destination . DS . 'bin' . DS . '*'); + + foreach ($binaries as $bin) { + chmod($bin, $dir_perms); + $io->writeln(' bin/' . Utils::basename($bin) . ' permissions reset to ' . decoct($dir_perms)); + } + + $io->newLine(); + + return 0; + } + + /** + * @return bool + */ + private function check(): bool + { + $success = true; + $io = $this->getIO(); + + if (!file_exists($this->destination)) { + $io->writeln(' file: ' . $this->destination . ' does not exist!'); + $success = false; + } + + foreach ($this->directories as $dir) { + if (!file_exists($this->destination . $dir)) { + $io->writeln(' directory: ' . $dir . ' does not exist!'); + $success = false; + } + } + + foreach ($this->mappings as $target => $link) { + if (!file_exists($this->destination . $target)) { + $io->writeln(' mappings: ' . $target . ' does not exist!'); + $success = false; + } + } + + if (!$success) { + $io->newLine(); + $io->writeln('install should be run with --symlink|--s to symlink first'); + } + + return $success; + } +} diff --git a/system/src/Grav/Console/Cli/SchedulerCommand.php b/system/src/Grav/Console/Cli/SchedulerCommand.php new file mode 100644 index 0000000..f7ebc39 --- /dev/null +++ b/system/src/Grav/Console/Cli/SchedulerCommand.php @@ -0,0 +1,276 @@ +setName('scheduler') + ->addOption( + 'install', + 'i', + InputOption::VALUE_NONE, + 'Show Install Command' + ) + ->addOption( + 'jobs', + 'j', + InputOption::VALUE_NONE, + 'Show Jobs Summary' + ) + ->addOption( + 'details', + 'd', + InputOption::VALUE_NONE, + 'Show Job Details' + ) + ->addOption( + 'run', + 'r', + InputOption::VALUE_OPTIONAL, + 'Force run all jobs or a specific job if you specify a specific Job ID', + false + ) + ->addOption( + 'force', + 'f', + InputOption::VALUE_NONE, + 'Force all due jobs to run regardless of their schedule' + ) + ->setDescription('Run the Grav Scheduler. Best when integrated with system cron') + ->setHelp("Running without any options will process the Scheduler jobs based on their cron schedule. Use --force to run all jobs immediately."); + } + + /** + * @return int + */ + protected function serve(): int + { + $this->initializePlugins(); + + $grav = Grav::instance(); + $grav['backups']->init(); + $this->initializePages(); + $this->initializeThemes(); + + /** @var Scheduler $scheduler */ + $scheduler = $grav['scheduler']; + $grav->fireEvent('onSchedulerInitialized', new Event(['scheduler' => $scheduler])); + + $input = $this->getInput(); + $io = $this->getIO(); + $error = 0; + + $run = $input->getOption('run'); + $showDetails = $input->getOption('details'); + $showJobs = $input->getOption('jobs'); + $forceRun = $input->getOption('force'); + + // Handle running jobs first if -r flag is present + if ($run !== false) { + if ($run === null || $run === '') { + // Run all jobs when -r is provided without a specific job ID + $io->title('Force Run All Jobs'); + + $jobs = $scheduler->getAllJobs(); + $hasOutput = false; + + foreach ($jobs as $job) { + if ($job->getEnabled()) { + $io->section('Running: ' . $job->getId()); + $job->inForeground()->run(); + + if ($job->isSuccessful()) { + $io->success('Job ' . $job->getId() . ' ran successfully'); + } else { + $error = 1; + $io->error('Job ' . $job->getId() . ' failed to run'); + } + + $output = $job->getOutput(); + if ($output) { + $io->write($output); + $hasOutput = true; + } + } + } + + if (!$hasOutput) { + $io->note('All enabled jobs completed'); + } + } else { + // Run specific job + $io->title('Force Run Job: ' . $run); + + $job = $scheduler->getJob($run); + + if ($job) { + $job->inForeground()->run(); + + if ($job->isSuccessful()) { + $io->success('Job ran successfully...'); + } else { + $error = 1; + $io->error('Job failed to run successfully...'); + } + + $output = $job->getOutput(); + + if ($output) { + $io->write($output); + } + } else { + $error = 1; + $io->error('Could not find a job with id: ' . $run); + } + } + + // Add separator if we're going to show details after + if ($showDetails) { + $io->newLine(); + } + } + + if ($showJobs) { + // Show jobs list + + $jobs = $scheduler->getAllJobs(); + $job_states = (array)$scheduler->getJobStates()->content(); + $rows = []; + + $table = new Table($io); + $table->setStyle('box'); + $headers = ['Job ID', 'Command', 'Run At', 'Status', 'Last Run', 'State']; + + $io->title('Scheduler Jobs Listing'); + + foreach ($jobs as $job) { + $job_status = ucfirst($job_states[$job->getId()]['state'] ?? 'ready'); + $last_run = $job_states[$job->getId()]['last-run'] ?? 0; + $status = $job_status === 'Failure' ? "{$job_status}" : "{$job_status}"; + $state = $job->getEnabled() ? 'Enabled' : 'Disabled'; + $row = [ + $job->getId(), + "{$job->getCommand()}", + "{$job->getAt()}", + $status, + '' . ($last_run === 0 ? 'Never' : date('Y-m-d H:i', $last_run)) . '', + $state, + + ]; + $rows[] = $row; + } + + if (!empty($rows)) { + $table->setHeaders($headers); + $table->setRows($rows); + $table->render(); + } else { + $io->text('no jobs found...'); + } + + $io->newLine(); + $io->note('For error details run "bin/grav scheduler -d"'); + $io->newLine(); + } + + if ($showDetails) { + $jobs = $scheduler->getAllJobs(); + $job_states = (array)$scheduler->getJobStates()->content(); + + $io->title('Job Details'); + + $table = new Table($io); + $table->setStyle('box'); + $table->setHeaders(['Job ID', 'Last Run', 'Next Run', 'Errors']); + $rows = []; + + foreach ($jobs as $job) { + $job_state = $job_states[$job->getId()]; + $error = isset($job_state['error']) ? trim($job_state['error']) : false; + + /** @var CronExpression $expression */ + $expression = $job->getCronExpression(); + $next_run = $expression->getNextRunDate(); + + $row = []; + $row[] = $job->getId(); + if (!is_null($job_state['last-run'])) { + $row[] = '' . date('Y-m-d H:i', $job_state['last-run']) . ''; + } else { + $row[] = 'Never'; + } + $row[] = '' . $next_run->format('Y-m-d H:i') . ''; + + if ($error) { + $row[] = "{$error}"; + } else { + $row[] = 'None'; + } + $rows[] = $row; + } + + $table->setRows($rows); + $table->render(); + } + + if ($input->getOption('install')) { + $io->title('Install Scheduler'); + + $verb = 'install'; + + if ($scheduler->isCrontabSetup()) { + $io->success('All Ready! You have already set up Grav\'s Scheduler in your crontab. You can validate this by running "crontab -l" to list your current crontab entries.'); + $verb = 'reinstall'; + } else { + $user = $scheduler->whoami(); + $error = 1; + $io->error('Can\'t find a crontab for ' . $user . '. You need to set up Grav\'s Scheduler in your crontab'); + } + if (!Utils::isWindows()) { + $io->note("To $verb, run the following command from your terminal:"); + $io->newLine(); + $io->text(trim($scheduler->getCronCommand())); + } else { + $io->note("To $verb, create a scheduled task in Windows."); + $io->text('Learn more at https://learn.getgrav.org/advanced/scheduler'); + } + } elseif (!$showJobs && !$showDetails && $run === false) { + // Run scheduler only if no other options were provided + $scheduler->run(null, $forceRun); + + if ($input->getOption('verbose')) { + $io->title('Running Scheduled Jobs'); + $io->text($scheduler->getVerboseOutput()); + } + } + + return $error; + } +} diff --git a/system/src/Grav/Console/Cli/SecurityCommand.php b/system/src/Grav/Console/Cli/SecurityCommand.php new file mode 100644 index 0000000..bd2c4e7 --- /dev/null +++ b/system/src/Grav/Console/Cli/SecurityCommand.php @@ -0,0 +1,102 @@ +setName('security') + ->setDescription('Capable of running various Security checks') + ->setHelp('The security runs various security checks on your Grav site'); + } + + /** + * @return int + */ + protected function serve(): int + { + $this->initializePages(); + + $io = $this->getIO(); + + /** @var Grav $grav */ + $grav = Grav::instance(); + $this->progress = new ProgressBar($this->output, count($grav['pages']->routes()) - 1); + $this->progress->setFormat('Scanning %current% pages [%bar%] %percent:3s%% %elapsed:6s%'); + $this->progress->setBarWidth(100); + + $io->title('Grav Security Check'); + $io->newline(2); + + $output = Security::detectXssFromPages($grav['pages'], false, [$this, 'outputProgress']); + + $error = 0; + if (!empty($output)) { + $counter = 1; + foreach ($output as $route => $results) { + $results_parts = array_map(static function ($value, $key) { + return $key.': \''.$value . '\''; + }, array_values($results), array_keys($results)); + + $io->writeln($counter++ .' - ' . $route . '' . implode(', ', $results_parts) . ''); + } + + $error = 1; + $io->error('Security Scan complete: ' . count($output) . ' potential XSS issues found...'); + } else { + $io->success('Security Scan complete: No issues found...'); + } + + $io->newline(1); + + return $error; + } + + /** + * @param array $args + * @return void + */ + public function outputProgress(array $args): void + { + switch ($args['type']) { + case 'count': + $steps = $args['steps']; + $freq = (int)($steps > 100 ? round($steps / 100) : $steps); + $this->progress->setMaxSteps($steps); + $this->progress->setRedrawFrequency($freq); + break; + case 'progress': + if (isset($args['complete']) && $args['complete']) { + $this->progress->finish(); + } else { + $this->progress->advance(); + } + break; + } + } +} diff --git a/system/src/Grav/Console/Cli/ServerCommand.php b/system/src/Grav/Console/Cli/ServerCommand.php new file mode 100644 index 0000000..fc373c0 --- /dev/null +++ b/system/src/Grav/Console/Cli/ServerCommand.php @@ -0,0 +1,154 @@ +setName('server') + ->addOption('port', 'p', InputOption::VALUE_OPTIONAL, 'Preferred HTTP port rather than auto-find (default is 8000-9000') + ->addOption('symfony', null, InputOption::VALUE_NONE, 'Force using Symfony server') + ->addOption('php', null, InputOption::VALUE_NONE, 'Force using built-in PHP server') + ->setDescription("Runs built-in web-server, Symfony first, then tries PHP's") + ->setHelp("Runs built-in web-server, Symfony first, then tries PHP's"); + } + + /** + * @return int + */ + protected function serve(): int + { + $input = $this->getInput(); + $io = $this->getIO(); + + $io->title('Grav Web Server'); + + // Ensure CLI colors are on + ini_set('cli_server.color', 'on'); + + // Options + $force_symfony = $input->getOption('symfony'); + $force_php = $input->getOption('php'); + + // Find PHP + $executableFinder = new PhpExecutableFinder(); + $php = $executableFinder->find(false); + + $this->ip = '127.0.0.1'; + $this->port = (int)($input->getOption('port') ?? 8000); + + // Get an open port + while (!$this->portAvailable($this->ip, $this->port)) { + $this->port++; + } + + // Setup the commands + $symfony_cmd = ['symfony', 'server:start', '--ansi', '--port=' . $this->port]; + $php_cmd = [$php, '-S', $this->ip.':'.$this->port, 'system/router.php']; + + $commands = [ + self::SYMFONY_SERVER => $symfony_cmd, + self::PHP_SERVER => $php_cmd + ]; + + if ($force_symfony) { + unset($commands[self::PHP_SERVER]); + } elseif ($force_php) { + unset($commands[self::SYMFONY_SERVER]); + } + + $error = 0; + foreach ($commands as $name => $command) { + $process = $this->runProcess($name, $command); + if (!$process) { + $io->note('Starting ' . $name . '...'); + } + + // Should only get here if there's an error running + if (!$process->isRunning() && (($name === self::SYMFONY_SERVER && $force_symfony) || ($name === self::PHP_SERVER))) { + $error = 1; + $io->error('Could not start ' . $name); + } + } + + return $error; + } + + /** + * @param string $name + * @param array $cmd + * @return Process + */ + protected function runProcess(string $name, array $cmd): Process + { + $io = $this->getIO(); + + $process = new Process($cmd); + $process->setTimeout(0); + $process->start(); + + if ($name === self::SYMFONY_SERVER && Utils::contains($process->getErrorOutput(), 'symfony: not found')) { + $io->error('The symfony binary could not be found, please install the CLI tools: https://symfony.com/download'); + $io->warning('Falling back to PHP web server...'); + } + + if ($name === self::PHP_SERVER) { + $io->success('Built-in PHP web server listening on http://' . $this->ip . ':' . $this->port . ' (PHP v' . PHP_VERSION . ')'); + } + + $process->wait(function ($type, $buffer) { + $this->getIO()->write($buffer); + }); + + return $process; + } + + /** + * Simple function test the port + * + * @param string $ip + * @param int $port + * @return bool + */ + protected function portAvailable(string $ip, int $port): bool + { + $fp = @fsockopen($ip, $port, $errno, $errstr, 0.1); + if (!$fp) { + return true; + } + + fclose($fp); + + return false; + } +} diff --git a/system/src/Grav/Console/Cli/YamlLinterCommand.php b/system/src/Grav/Console/Cli/YamlLinterCommand.php new file mode 100644 index 0000000..c794d84 --- /dev/null +++ b/system/src/Grav/Console/Cli/YamlLinterCommand.php @@ -0,0 +1,124 @@ +setName('yamllinter') + ->addOption( + 'all', + 'a', + InputOption::VALUE_NONE, + 'Go through the whole Grav installation' + ) + ->addOption( + 'folder', + 'f', + InputOption::VALUE_OPTIONAL, + 'Go through specific folder' + ) + ->setDescription('Checks various files for YAML errors') + ->setHelp('Checks various files for YAML errors'); + } + + /** + * @return int + */ + protected function serve(): int + { + $input = $this->getInput(); + $io = $this->getIO(); + + $io->title('Yaml Linter'); + + $error = 0; + if ($input->getOption('all')) { + $io->section('All'); + $errors = YamlLinter::lint(''); + + if (empty($errors)) { + $io->success('No YAML Linting issues found'); + } else { + $error = 1; + $this->displayErrors($errors, $io); + } + } elseif ($folder = $input->getOption('folder')) { + $io->section($folder); + $errors = YamlLinter::lint($folder); + + if (empty($errors)) { + $io->success('No YAML Linting issues found'); + } else { + $error = 1; + $this->displayErrors($errors, $io); + } + } else { + $io->section('User Configuration'); + $errors = YamlLinter::lintConfig(); + + if (empty($errors)) { + $io->success('No YAML Linting issues with configuration'); + } else { + $error = 1; + $this->displayErrors($errors, $io); + } + + $io->section('Pages Frontmatter'); + $errors = YamlLinter::lintPages(); + + if (empty($errors)) { + $io->success('No YAML Linting issues with pages'); + } else { + $error = 1; + $this->displayErrors($errors, $io); + } + + $io->section('Page Blueprints'); + $errors = YamlLinter::lintBlueprints(); + + if (empty($errors)) { + $io->success('No YAML Linting issues with blueprints'); + } else { + $error = 1; + $this->displayErrors($errors, $io); + } + } + + return $error; + } + + /** + * @param array $errors + * @param SymfonyStyle $io + * @return void + */ + protected function displayErrors(array $errors, SymfonyStyle $io): void + { + $io->error('YAML Linting issues found...'); + foreach ($errors as $path => $error) { + $io->writeln("{$path} - {$error}"); + } + } +} diff --git a/system/src/Grav/Console/ConsoleCommand.php b/system/src/Grav/Console/ConsoleCommand.php new file mode 100644 index 0000000..beee3d0 --- /dev/null +++ b/system/src/Grav/Console/ConsoleCommand.php @@ -0,0 +1,46 @@ +setupConsole($input, $output); + + return $this->serve(); + } + + /** + * Override with your implementation. + * + * @return int + */ + protected function serve() + { + // Return error. + return 1; + } +} diff --git a/system/src/Grav/Console/ConsoleTrait.php b/system/src/Grav/Console/ConsoleTrait.php new file mode 100644 index 0000000..43aadd9 --- /dev/null +++ b/system/src/Grav/Console/ConsoleTrait.php @@ -0,0 +1,338 @@ +argv = $_SERVER['argv'][0]; + $this->input = $input; + $this->output = new SymfonyStyle($input, $output); + + $this->setupGrav(); + } + + public function getInput(): InputInterface + { + return $this->input; + } + + /** + * @return SymfonyStyle + */ + public function getIO(): SymfonyStyle + { + return $this->output; + } + + /** + * Adds an option. + * + * @param string $name The option name + * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param int|null $mode The option mode: One of the InputOption::VALUE_* constants + * @param string $description A description text + * @param string|string[]|int|bool|null $default The default value (must be null for InputOption::VALUE_NONE) + * @return $this + * @throws InvalidArgumentException If option mode is invalid or incompatible + */ + public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null) + { + if ($name !== 'env' && $name !== 'lang') { + parent::addOption($name, $shortcut, $mode, $description, $default); + } + + return $this; + } + + /** + * @return void + */ + final protected function setupGrav(): void + { + try { + $language = $this->input->getOption('lang'); + if ($language) { + // Set used language. + $this->setLanguage($language); + } + } catch (InvalidArgumentException $e) {} + + // Initialize cache with CLI compatibility + $grav = Grav::instance(); + $grav['config']->set('system.cache.cli_compatibility', true); + } + + /** + * Initialize Grav. + * + * - Load configuration + * - Initialize logger + * - Disable debugger + * - Set timezone, locale + * - Load plugins (call PluginsLoadedEvent) + * - Set Pages and Users type to be used in the site + * + * Safe to be called multiple times. + * + * @return $this + */ + final protected function initializeGrav() + { + InitializeProcessor::initializeCli(Grav::instance()); + + return $this; + } + + /** + * Set language to be used in CLI. + * + * @param string|null $code + * @return $this + */ + final protected function setLanguage(string $code = null) + { + $this->initializeGrav(); + + $grav = Grav::instance(); + /** @var Language $language */ + $language = $grav['language']; + if ($language->enabled()) { + if ($code && $language->validate($code)) { + $language->setActive($code); + } else { + $language->setActive($language->getDefault()); + } + } + + return $this; + } + + /** + * Properly initialize plugins. + * + * - call $this->initializeGrav() + * - call onPluginsInitialized event + * + * Safe to be called multiple times. + * + * @return $this + */ + final protected function initializePlugins() + { + if (!$this->plugins_initialized) { + $this->plugins_initialized = true; + + $this->initializeGrav(); + + // Initialize plugins. + $grav = Grav::instance(); + $grav['plugins']->init(); + $grav->fireEvent('onPluginsInitialized'); + } + + return $this; + } + + /** + * Properly initialize themes. + * + * - call $this->initializePlugins() + * - initialize theme (call onThemeInitialized event) + * + * Safe to be called multiple times. + * + * @return $this + */ + final protected function initializeThemes() + { + if (!$this->themes_initialized) { + $this->themes_initialized = true; + + $this->initializePlugins(); + + // Initialize themes. + $grav = Grav::instance(); + $grav['themes']->init(); + } + + return $this; + } + + /** + * Properly initialize pages. + * + * - call $this->initializeThemes() + * - initialize assets (call onAssetsInitialized event) + * - initialize twig (calls the twig events) + * - initialize pages (calls onPagesInitialized event) + * + * Safe to be called multiple times. + * + * @return $this + */ + final protected function initializePages() + { + if (!$this->pages_initialized) { + $this->pages_initialized = true; + + $this->initializeThemes(); + + $grav = Grav::instance(); + + // Initialize assets. + $grav['assets']->init(); + $grav->fireEvent('onAssetsInitialized'); + + // Initialize twig. + $grav['twig']->init(); + + // Initialize pages. + $pages = $grav['pages']; + $pages->init(); + $grav->fireEvent('onPagesInitialized', new Event(['pages' => $pages])); + } + + return $this; + } + + /** + * @param string $path + * @return void + */ + public function isGravInstance($path) + { + $io = $this->getIO(); + + if (!file_exists($path)) { + $io->writeln(''); + $io->writeln("ERROR: Destination doesn't exist:"); + $io->writeln(" $path"); + $io->writeln(''); + exit; + } + + if (!is_dir($path)) { + $io->writeln(''); + $io->writeln("ERROR: Destination chosen to install is not a directory:"); + $io->writeln(" $path"); + $io->writeln(''); + exit; + } + + if (!file_exists($path . DS . 'index.php') || !file_exists($path . DS . '.dependencies') || !file_exists($path . DS . 'system' . DS . 'config' . DS . 'system.yaml')) { + $io->writeln(''); + $io->writeln('ERROR: Destination chosen to install does not appear to be a Grav instance:'); + $io->writeln(" $path"); + $io->writeln(''); + exit; + } + } + + /** + * @param string $path + * @param string $action + * @return string|false + */ + public function composerUpdate($path, $action = 'install') + { + $composer = Composer::getComposerExecutor(); + + return system($composer . ' --working-dir=' . escapeshellarg($path) . ' --no-interaction --no-dev --prefer-dist -o '. $action); + } + + /** + * @param array $all + * @return int + * @throws Exception + */ + public function clearCache($all = []) + { + if ($all) { + $all = ['--all' => true]; + } + + $command = new ClearCacheCommand(); + $input = new ArrayInput($all); + return $command->run($input, $this->output); + } + + /** + * @return void + */ + public function invalidateCache() + { + Cache::invalidateCache(); + } + + /** + * Load the local config file + * + * @return string|false The local config file name. false if local config does not exist + */ + public function loadLocalConfig() + { + $home_folder = getenv('HOME') ?: getenv('HOMEDRIVE') . getenv('HOMEPATH'); + $local_config_file = $home_folder . '/.grav/config'; + + if (file_exists($local_config_file)) { + $file = YamlFile::instance($local_config_file); + $this->local_config = $file->content(); + $file->free(); + + return $local_config_file; + } + + return false; + } +} diff --git a/system/src/Grav/Console/Gpm/DirectInstallCommand.php b/system/src/Grav/Console/Gpm/DirectInstallCommand.php new file mode 100644 index 0000000..74a0c6b --- /dev/null +++ b/system/src/Grav/Console/Gpm/DirectInstallCommand.php @@ -0,0 +1,321 @@ +setName('direct-install') + ->setAliases(['directinstall']) + ->addArgument( + 'package-file', + InputArgument::REQUIRED, + 'Installable package local or remote . Can install specific version' + ) + ->addOption( + 'all-yes', + 'y', + InputOption::VALUE_NONE, + 'Assumes yes (or best approach) instead of prompting' + ) + ->addOption( + 'destination', + 'd', + InputOption::VALUE_OPTIONAL, + 'The destination where the package should be installed at. By default this would be where the grav instance has been launched from', + GRAV_ROOT + ) + ->setDescription('Installs Grav, plugin, or theme directly from a file or a URL') + ->setHelp('The direct-install command installs Grav, plugin, or theme directly from a file or a URL'); + } + + /** + * @return int + */ + protected function serve(): int + { + $input = $this->getInput(); + $io = $this->getIO(); + + if (!class_exists(ZipArchive::class)) { + $io->title('Direct Install'); + $io->error('php-zip extension needs to be enabled!'); + + return 1; + } + + // Making sure the destination is usable + $this->destination = realpath($input->getOption('destination')); + + if (!Installer::isGravInstance($this->destination) || + !Installer::isValidDestination($this->destination, [Installer::EXISTS, Installer::IS_LINK]) + ) { + $io->writeln('ERROR: ' . Installer::lastErrorMsg()); + + return 1; + } + + $this->all_yes = $input->getOption('all-yes'); + + $package_file = $input->getArgument('package-file'); + + $question = new ConfirmationQuestion("Are you sure you want to direct-install {$package_file} [y|N] ", false); + + $answer = $this->all_yes ? true : $io->askQuestion($question); + + if (!$answer) { + $io->writeln('exiting...'); + $io->newLine(); + + return 1; + } + + $tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true); + $tmp_zip = $tmp_dir . uniqid('/Grav-', false); + + $io->newLine(); + $io->writeln("Preparing to install {$package_file}"); + + $zip = null; + if (Response::isRemote($package_file)) { + $io->write(' |- Downloading package... 0%'); + try { + $zip = GPM::downloadPackage($package_file, $tmp_zip); + } catch (RuntimeException $e) { + $io->newLine(); + $io->writeln(" `- ERROR: {$e->getMessage()}"); + $io->newLine(); + + return 1; + } + + if ($zip) { + $io->write("\x0D"); + $io->write(' |- Downloading package... 100%'); + $io->newLine(); + } + } elseif (is_file($package_file)) { + $io->write(' |- Copying package... 0%'); + $zip = GPM::copyPackage($package_file, $tmp_zip); + if ($zip) { + $io->write("\x0D"); + $io->write(' |- Copying package... 100%'); + $io->newLine(); + } + } + + if ($zip && file_exists($zip)) { + $tmp_source = $tmp_dir . uniqid('/Grav-', false); + + $io->write(' |- Extracting package... '); + $extracted = Installer::unZip($zip, $tmp_source); + + if (!$extracted) { + $io->write("\x0D"); + $io->writeln(' |- Extracting package... failed'); + Folder::delete($tmp_source); + Folder::delete($tmp_zip); + + return 1; + } + + $io->write("\x0D"); + $io->writeln(' |- Extracting package... ok'); + + + $type = GPM::getPackageType($extracted); + + if (!$type) { + $io->writeln(" '- ERROR: Not a valid Grav package"); + $io->newLine(); + Folder::delete($tmp_source); + Folder::delete($tmp_zip); + + return 1; + } + + $blueprint = GPM::getBlueprints($extracted); + if ($blueprint) { + if (isset($blueprint['dependencies'])) { + $dependencies = []; + foreach ($blueprint['dependencies'] as $dependency) { + if (is_array($dependency)) { + if (isset($dependency['name'])) { + $dependencies[] = $dependency['name']; + } + if (isset($dependency['github'])) { + $dependencies[] = $dependency['github']; + } + } else { + $dependencies[] = $dependency; + } + } + $io->writeln(' |- Dependencies found... [' . implode(',', $dependencies) . ']'); + + $question = new ConfirmationQuestion(" | '- Dependencies will not be satisfied. Continue ? [y|N] ", false); + $answer = $this->all_yes ? true : $io->askQuestion($question); + + if (!$answer) { + $io->writeln('exiting...'); + $io->newLine(); + Folder::delete($tmp_source); + Folder::delete($tmp_zip); + + return 1; + } + } + } + + if ($type === 'grav') { + $io->write(' |- Checking destination... '); + Installer::isValidDestination(GRAV_ROOT . '/system'); + if (Installer::IS_LINK === Installer::lastErrorCode()) { + $io->write("\x0D"); + $io->writeln(' |- Checking destination... symbolic link'); + $io->writeln(" '- ERROR: symlinks found... " . GRAV_ROOT . ''); + $io->newLine(); + Folder::delete($tmp_source); + Folder::delete($tmp_zip); + + return 1; + } + + $io->write("\x0D"); + $io->writeln(' |- Checking destination... ok'); + + $io->write(' |- Installing package... '); + + $this->upgradeGrav($zip, $extracted); + } else { + $name = GPM::getPackageName($extracted); + + if (!$name) { + $io->writeln('ERROR: Name could not be determined. Please specify with --name|-n'); + $io->newLine(); + Folder::delete($tmp_source); + Folder::delete($tmp_zip); + + return 1; + } + + $install_path = GPM::getInstallPath($type, $name); + $is_update = file_exists($install_path); + + $io->write(' |- Checking destination... '); + + Installer::isValidDestination(GRAV_ROOT . DS . $install_path); + if (Installer::lastErrorCode() === Installer::IS_LINK) { + $io->write("\x0D"); + $io->writeln(' |- Checking destination... symbolic link'); + $io->writeln(" '- ERROR: symlink found... " . GRAV_ROOT . DS . $install_path . ''); + $io->newLine(); + Folder::delete($tmp_source); + Folder::delete($tmp_zip); + + return 1; + } + + $io->write("\x0D"); + $io->writeln(' |- Checking destination... ok'); + + $io->write(' |- Installing package... '); + + Installer::install( + $zip, + $this->destination, + $options = [ + 'install_path' => $install_path, + 'theme' => (($type === 'theme')), + 'is_update' => $is_update + ], + $extracted + ); + + // clear cache after successful upgrade + $this->clearCache(); + } + + Folder::delete($tmp_source); + + $io->write("\x0D"); + + if (Installer::lastErrorCode()) { + $io->writeln(" '- " . Installer::lastErrorMsg() . ''); + $io->newLine(); + } else { + $io->writeln(' |- Installing package... ok'); + $io->writeln(" '- Success! "); + $io->newLine(); + } + } else { + $io->writeln(" '- ERROR: ZIP package could not be found"); + Folder::delete($tmp_zip); + + return 1; + } + + Folder::delete($tmp_zip); + + return 0; + } + + /** + * @param string $zip + * @param string $folder + * @return void + */ + private function upgradeGrav(string $zip, string $folder): void + { + if (!is_dir($folder)) { + Installer::setError('Invalid source folder'); + } + + try { + $script = $folder . '/system/install.php'; + /** Install $installer */ + if ((file_exists($script) && $install = include $script) && is_callable($install)) { + $install($zip); + } else { + throw new RuntimeException('Uploaded archive file is not a valid Grav update package'); + } + } catch (Exception $e) { + Installer::setError($e->getMessage()); + } + } +} diff --git a/system/src/Grav/Console/Gpm/IndexCommand.php b/system/src/Grav/Console/Gpm/IndexCommand.php new file mode 100644 index 0000000..082fe00 --- /dev/null +++ b/system/src/Grav/Console/Gpm/IndexCommand.php @@ -0,0 +1,335 @@ +setName('index') + ->addOption( + 'force', + 'f', + InputOption::VALUE_NONE, + 'Force re-fetching the data from remote' + ) + ->addOption( + 'filter', + 'F', + InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, + 'Allows to limit the results based on one or multiple filters input. This can be either portion of a name/slug or a regex' + ) + ->addOption( + 'themes-only', + 'T', + InputOption::VALUE_NONE, + 'Filters the results to only Themes' + ) + ->addOption( + 'plugins-only', + 'P', + InputOption::VALUE_NONE, + 'Filters the results to only Plugins' + ) + ->addOption( + 'updates-only', + 'U', + InputOption::VALUE_NONE, + 'Filters the results to Updatable Themes and Plugins only' + ) + ->addOption( + 'installed-only', + 'I', + InputOption::VALUE_NONE, + 'Filters the results to only the Themes and Plugins you have installed' + ) + ->addOption( + 'sort', + 's', + InputOption::VALUE_REQUIRED, + 'Allows to sort (ASC) the results. SORT can be either "name", "slug", "author", "date"', + 'date' + ) + ->addOption( + 'desc', + 'D', + InputOption::VALUE_NONE, + 'Reverses the order of the output.' + ) + ->addOption( + 'enabled', + 'e', + InputOption::VALUE_NONE, + 'Filters the results to only enabled Themes and Plugins.' + ) + ->addOption( + 'disabled', + 'd', + InputOption::VALUE_NONE, + 'Filters the results to only disabled Themes and Plugins.' + ) + ->setDescription('Lists the plugins and themes available for installation') + ->setHelp('The index command lists the plugins and themes available for installation') + ; + } + + /** + * @return int + */ + protected function serve(): int + { + $input = $this->getInput(); + $this->options = $input->getOptions(); + $this->gpm = new GPM($this->options['force']); + $this->displayGPMRelease(); + $this->data = $this->gpm->getRepository(); + + $data = $this->filter($this->data); + + $io = $this->getIO(); + + if (count($data) === 0) { + $io->writeln('No data was found in the GPM repository stored locally.'); + $io->writeln('Please try clearing cache and running the bin/gpm index -f command again'); + $io->writeln('If this doesn\'t work try tweaking your GPM system settings.'); + $io->newLine(); + $io->writeln('For more help go to:'); + $io->writeln(' -> https://learn.getgrav.org/troubleshooting/common-problems#cannot-connect-to-the-gpm'); + + return 1; + } + + foreach ($data as $type => $packages) { + $io->writeln('' . strtoupper($type) . ' [ ' . count($packages) . ' ]'); + + $packages = $this->sort($packages); + + if (!empty($packages)) { + $io->section('Packages table'); + $table = new Table($io); + $table->setHeaders(['Count', 'Name', 'Slug', 'Version', 'Installed', 'Enabled']); + + $index = 0; + foreach ($packages as $slug => $package) { + $row = [ + 'Count' => $index++ + 1, + 'Name' => '' . Utils::truncate($package->name, 20, false, ' ', '...') . ' ', + 'Slug' => $slug, + 'Version'=> $this->version($package), + 'Installed' => $this->installed($package), + 'Enabled' => $this->enabled($package), + ]; + + $table->addRow($row); + } + + $table->render(); + } + + $io->newLine(); + } + + $io->writeln('You can either get more informations about a package by typing:'); + $io->writeln(" {$this->argv} info "); + $io->newLine(); + $io->writeln('Or you can install a package by typing:'); + $io->writeln(" {$this->argv} install "); + $io->newLine(); + + return 0; + } + + /** + * @param Package $package + * @return string + */ + private function version(Package $package): string + { + $list = $this->gpm->{'getUpdatable' . ucfirst($package->package_type)}(); + $package = $list[$package->slug] ?? $package; + $type = ucfirst(preg_replace('/s$/', '', $package->package_type)); + $updatable = $this->gpm->{'is' . $type . 'Updatable'}($package->slug); + $installed = $this->gpm->{'is' . $type . 'Installed'}($package->slug); + $local = $this->gpm->{'getInstalled' . $type}($package->slug); + + if (!$installed || !$updatable) { + $version = $installed ? $local->version : $package->version; + return "v{$version}"; + } + + return "v{$package->version} -> v{$package->available}"; + } + + /** + * @param Package $package + * @return string + */ + private function installed(Package $package): string + { + $type = ucfirst(preg_replace('/s$/', '', $package->package_type)); + $method = 'is' . $type . 'Installed'; + $installed = $this->gpm->{$method}($package->slug); + + return !$installed ? 'not installed' : 'installed'; + } + + /** + * @param Package $package + * @return string + */ + private function enabled(Package $package): string + { + $type = ucfirst(preg_replace('/s$/', '', $package->package_type)); + $method = 'is' . $type . 'Installed'; + $installed = $this->gpm->{$method}($package->slug); + + $result = ''; + if ($installed) { + $method = 'is' . $type . 'Enabled'; + $enabled = $this->gpm->{$method}($package->slug); + if ($enabled === true) { + $result = 'enabled'; + } elseif ($enabled === false) { + $result = 'disabled'; + } + } + + return $result; + } + + /** + * @param Packages $data + * @return Packages + */ + public function filter(Packages $data): Packages + { + // filtering and sorting + if ($this->options['plugins-only']) { + unset($data['themes']); + } + if ($this->options['themes-only']) { + unset($data['plugins']); + } + + $filter = [ + $this->options['desc'], + $this->options['disabled'], + $this->options['enabled'], + $this->options['filter'], + $this->options['installed-only'], + $this->options['updates-only'], + ]; + + if (count(array_filter($filter))) { + foreach ($data as $type => $packages) { + foreach ($packages as $slug => $package) { + $filter = true; + + // Filtering by string + if ($this->options['filter']) { + $filter = preg_grep('/(' . implode('|', $this->options['filter']) . ')/i', [$slug, $package->name]); + } + + // Filtering updatables only + if ($filter && ($this->options['installed-only'] || $this->options['enabled'] || $this->options['disabled'])) { + $method = ucfirst(preg_replace('/s$/', '', $package->package_type)); + $function = 'is' . $method . 'Installed'; + $filter = $this->gpm->{$function}($package->slug); + } + + // Filtering updatables only + if ($filter && $this->options['updates-only']) { + $method = ucfirst(preg_replace('/s$/', '', $package->package_type)); + $function = 'is' . $method . 'Updatable'; + $filter = $this->gpm->{$function}($package->slug); + } + + // Filtering enabled only + if ($filter && $this->options['enabled']) { + $method = ucfirst(preg_replace('/s$/', '', $package->package_type)); + + // Check if packaged is enabled. + $function = 'is' . $method . 'Enabled'; + $filter = $this->gpm->{$function}($package->slug); + } + + // Filtering disabled only + if ($filter && $this->options['disabled']) { + $method = ucfirst(preg_replace('/s$/', '', $package->package_type)); + + // Check if package is disabled. + $function = 'is' . $method . 'Enabled'; + $enabled_filter = $this->gpm->{$function}($package->slug); + + // Apply filtering results. + if (!( $enabled_filter === false)) { + $filter = false; + } + } + + if (!$filter) { + unset($data[$type][$slug]); + } + } + } + } + + return $data; + } + + /** + * @param AbstractPackageCollection|Plugins|Themes $packages + * @return array + */ + public function sort(AbstractPackageCollection $packages): array + { + $key = $this->options['sort']; + + // Sorting only works once. + return $packages->sort( + function ($a, $b) use ($key) { + switch ($key) { + case 'author': + return strcmp($a->{$key}['name'], $b->{$key}['name']); + default: + return strcmp($a->$key, $b->$key); + } + }, + $this->options['desc'] ? true : false + ); + } +} diff --git a/system/src/Grav/Console/Gpm/InfoCommand.php b/system/src/Grav/Console/Gpm/InfoCommand.php new file mode 100644 index 0000000..0eb6c66 --- /dev/null +++ b/system/src/Grav/Console/Gpm/InfoCommand.php @@ -0,0 +1,191 @@ +setName('info') + ->addOption( + 'force', + 'f', + InputOption::VALUE_NONE, + 'Force fetching the new data remotely' + ) + ->addOption( + 'all-yes', + 'y', + InputOption::VALUE_NONE, + 'Assumes yes (or best approach) instead of prompting' + ) + ->addArgument( + 'package', + InputArgument::REQUIRED, + 'The package of which more informations are desired. Use the "index" command for a list of packages' + ) + ->setDescription('Shows more informations about a package') + ->setHelp('The info shows more information about a package'); + } + + /** + * @return int + */ + protected function serve(): int + { + $input = $this->getInput(); + $io = $this->getIO(); + + $this->gpm = new GPM($input->getOption('force')); + + $this->all_yes = $input->getOption('all-yes'); + + $this->displayGPMRelease(); + + $foundPackage = $this->gpm->findPackage($input->getArgument('package')); + + if (!$foundPackage) { + $io->writeln("The package '{$input->getArgument('package')}' was not found in the Grav repository."); + $io->newLine(); + $io->writeln('You can list all the available packages by typing:'); + $io->writeln(" {$this->argv} index"); + $io->newLine(); + + return 1; + } + + $io->writeln("Found package '{$input->getArgument('package')}' under the '" . ucfirst($foundPackage->package_type) . "' section"); + $io->newLine(); + $io->writeln("{$foundPackage->name} [{$foundPackage->slug}]"); + $io->writeln(str_repeat('-', strlen($foundPackage->name) + strlen($foundPackage->slug) + 3)); + $io->writeln('' . strip_tags($foundPackage->description_plain) . ''); + $io->newLine(); + + $packageURL = ''; + if (isset($foundPackage->author['url'])) { + $packageURL = '<' . $foundPackage->author['url'] . '>'; + } + + $io->writeln('' . str_pad( + 'Author', + 12 + ) . ': ' . $foundPackage->author['name'] . ' <' . $foundPackage->author['email'] . '> ' . $packageURL); + + foreach ([ + 'version', + 'keywords', + 'date', + 'homepage', + 'demo', + 'docs', + 'guide', + 'repository', + 'bugs', + 'zipball_url', + 'license' + ] as $info) { + if (isset($foundPackage->{$info})) { + $name = ucfirst($info); + $data = $foundPackage->{$info}; + + if ($info === 'zipball_url') { + $name = 'Download'; + } + + if ($info === 'date') { + $name = 'Last Update'; + $data = date('D, j M Y, H:i:s, P ', strtotime($data)); + } + + $name = str_pad($name, 12); + $io->writeln("{$name}: {$data}"); + } + } + + $type = rtrim($foundPackage->package_type, 's'); + $updatable = $this->gpm->{'is' . $type . 'Updatable'}($foundPackage->slug); + $installed = $this->gpm->{'is' . $type . 'Installed'}($foundPackage->slug); + + // display current version if installed and different + if ($installed && $updatable) { + $local = $this->gpm->{'getInstalled'. $type}($foundPackage->slug); + $io->newLine(); + $io->writeln("Currently installed version: {$local->version}"); + $io->newLine(); + } + + // display changelog information + $question = new ConfirmationQuestion( + 'Would you like to read the changelog? [y|N] ', + false + ); + $answer = $this->all_yes ? true : $io->askQuestion($question); + + if ($answer) { + $changelog = $foundPackage->changelog; + + $io->newLine(); + foreach ($changelog as $version => $log) { + $title = $version . ' [' . $log['date'] . ']'; + $content = preg_replace_callback('/\d\.\s\[\]\(#(.*)\)/', static function ($match) { + return "\n" . ucfirst($match[1]) . ':'; + }, $log['content']); + + $io->writeln("{$title}"); + $io->writeln(str_repeat('-', strlen($title))); + $io->writeln($content); + $io->newLine(); + + $question = new ConfirmationQuestion('Press [ENTER] to continue or [q] to quit ', true); + $answer = $this->all_yes ? false : $io->askQuestion($question); + if (!$answer) { + break; + } + $io->newLine(); + } + } + + $io->newLine(); + + if ($installed && $updatable) { + $io->writeln('You can update this package by typing:'); + $io->writeln(" {$this->argv} update {$foundPackage->slug}"); + } else { + $io->writeln('You can install this package by typing:'); + $io->writeln(" {$this->argv} install {$foundPackage->slug}"); + } + + $io->newLine(); + + return 0; + } +} diff --git a/system/src/Grav/Console/Gpm/InstallCommand.php b/system/src/Grav/Console/Gpm/InstallCommand.php new file mode 100644 index 0000000..7c2a047 --- /dev/null +++ b/system/src/Grav/Console/Gpm/InstallCommand.php @@ -0,0 +1,726 @@ +setName('install') + ->addOption( + 'force', + 'f', + InputOption::VALUE_NONE, + 'Force re-fetching the data from remote' + ) + ->addOption( + 'all-yes', + 'y', + InputOption::VALUE_NONE, + 'Assumes yes (or best approach) instead of prompting' + ) + ->addOption( + 'destination', + 'd', + InputOption::VALUE_OPTIONAL, + 'The destination where the package should be installed at. By default this would be where the grav instance has been launched from', + GRAV_ROOT + ) + ->addArgument( + 'package', + InputArgument::IS_ARRAY | InputArgument::REQUIRED, + 'Package(s) to install. Use "bin/gpm index" to list packages. Use "bin/gpm direct-install" to install a specific version' + ) + ->setDescription('Performs the installation of plugins and themes') + ->setHelp('The install command allows to install plugins and themes'); + } + + /** + * Allows to set the GPM object, used for testing the class + * + * @param GPM $gpm + */ + public function setGpm(GPM $gpm): void + { + $this->gpm = $gpm; + } + + /** + * @return int + */ + protected function serve(): int + { + $input = $this->getInput(); + $io = $this->getIO(); + + if (!class_exists(ZipArchive::class)) { + $io->title('GPM Install'); + $io->error('php-zip extension needs to be enabled!'); + + return 1; + } + + $this->gpm = new GPM($input->getOption('force')); + + $this->all_yes = $input->getOption('all-yes'); + + $this->displayGPMRelease(); + + $this->destination = realpath($input->getOption('destination')); + + $packages = array_map('strtolower', $input->getArgument('package')); + $this->data = $this->gpm->findPackages($packages); + $this->loadLocalConfig(); + + if (!Installer::isGravInstance($this->destination) || + !Installer::isValidDestination($this->destination, [Installer::EXISTS, Installer::IS_LINK]) + ) { + $io->writeln('ERROR: ' . Installer::lastErrorMsg()); + + return 1; + } + + $io->newLine(); + + if (!$this->data['total']) { + $io->writeln('Nothing to install.'); + $io->newLine(); + + return 0; + } + + if (count($this->data['not_found'])) { + $io->writeln('These packages were not found on Grav: ' . implode( + ', ', + array_keys($this->data['not_found']) + ) . ''); + } + + unset($this->data['not_found'], $this->data['total']); + + if (null !== $this->local_config) { + // Symlinks available, ask if Grav should use them + $this->use_symlinks = false; + $question = new ConfirmationQuestion('Should Grav use the symlinks if available? [y|N] ', false); + + $answer = $this->all_yes ? false : $io->askQuestion($question); + + if ($answer) { + $this->use_symlinks = true; + } + } + + $io->newLine(); + + try { + $dependencies = $this->gpm->getDependencies($packages); + } catch (Exception $e) { + //Error out if there are incompatible packages requirements and tell which ones, and what to do + //Error out if there is any error in parsing the dependencies and their versions, and tell which one is broken + $io->writeln("{$e->getMessage()}"); + + return 1; + } + + if ($dependencies) { + try { + $this->installDependencies($dependencies, 'install', 'The following dependencies need to be installed...'); + $this->installDependencies($dependencies, 'update', 'The following dependencies need to be updated...'); + $this->installDependencies($dependencies, 'ignore', "The following dependencies can be updated as there is a newer version, but it's not mandatory...", false); + } catch (Exception $e) { + $io->writeln('Installation aborted'); + + return 1; + } + + $io->writeln('Dependencies are OK'); + $io->newLine(); + } + + + //We're done installing dependencies. Install the actual packages + foreach ($this->data as $data) { + foreach ($data as $package_name => $package) { + if (array_key_exists($package_name, $dependencies)) { + $io->writeln("Package {$package_name} already installed as dependency"); + } else { + $is_valid_destination = Installer::isValidDestination($this->destination . DS . $package->install_path); + if ($is_valid_destination || Installer::lastErrorCode() == Installer::NOT_FOUND) { + $this->processPackage($package, false); + } else { + if (Installer::lastErrorCode() == Installer::EXISTS) { + try { + $this->askConfirmationIfMajorVersionUpdated($package); + $this->gpm->checkNoOtherPackageNeedsThisDependencyInALowerVersion($package->slug, $package->available, array_keys($data)); + } catch (Exception $e) { + $io->writeln("{$e->getMessage()}"); + + return 1; + } + + $question = new ConfirmationQuestion("The package {$package_name} is already installed, overwrite? [y|N] ", false); + $answer = $this->all_yes ? true : $io->askQuestion($question); + + if ($answer) { + $is_update = true; + $this->processPackage($package, $is_update); + } else { + $io->writeln("Package {$package_name} not overwritten"); + } + } else { + if (Installer::lastErrorCode() == Installer::IS_LINK) { + $io->writeln("Cannot overwrite existing symlink for {$package_name}"); + $io->newLine(); + } + } + } + } + } + } + + if (count($this->demo_processing) > 0) { + foreach ($this->demo_processing as $package) { + $this->installDemoContent($package); + } + } + + // clear cache after successful upgrade + $this->clearCache(); + + return 0; + } + + /** + * If the package is updated from an older major release, show warning and ask confirmation + * + * @param Package $package + * @return void + */ + public function askConfirmationIfMajorVersionUpdated(Package $package): void + { + $io = $this->getIO(); + $package_name = $package->name; + $new_version = $package->available ?: $this->gpm->getLatestVersionOfPackage($package->slug); + $old_version = $package->version; + + $major_version_changed = explode('.', $new_version)[0] !== explode('.', $old_version)[0]; + + if ($major_version_changed) { + if ($this->all_yes) { + $io->writeln("The package {$package_name} will be updated to a new major version {$new_version}, from {$old_version}"); + return; + } + + $question = new ConfirmationQuestion("The package {$package_name} will be updated to a new major version {$new_version}, from {$old_version}. Be sure to read what changed with the new major release. Continue? [y|N] ", false); + + if (!$io->askQuestion($question)) { + $io->writeln("Package {$package_name} not updated"); + exit; + } + } + } + + /** + * Given a $dependencies list, filters their type according to $type and + * shows $message prior to listing them to the user. Then asks the user a confirmation prior + * to installing them. + * + * @param array $dependencies The dependencies array + * @param string $type The type of dependency to show: install, update, ignore + * @param string $message A message to be shown prior to listing the dependencies + * @param bool $required A flag that determines if the installation is required or optional + * @return void + * @throws Exception + */ + public function installDependencies(array $dependencies, string $type, string $message, bool $required = true): void + { + $io = $this->getIO(); + $packages = array_filter($dependencies, static function ($action) use ($type) { + return $action === $type; + }); + if (count($packages) > 0) { + $io->writeln($message); + + foreach ($packages as $dependencyName => $dependencyVersion) { + $io->writeln(" |- Package {$dependencyName}"); + } + + $io->newLine(); + + if ($type === 'install') { + $questionAction = 'Install'; + } else { + $questionAction = 'Update'; + } + + if (count($packages) === 1) { + $questionArticle = 'this'; + } else { + $questionArticle = 'these'; + } + + if (count($packages) === 1) { + $questionNoun = 'package'; + } else { + $questionNoun = 'packages'; + } + + $question = new ConfirmationQuestion("{$questionAction} {$questionArticle} {$questionNoun}? [Y|n] ", true); + $answer = $this->all_yes ? true : $io->askQuestion($question); + + if ($answer) { + foreach ($packages as $dependencyName => $dependencyVersion) { + $package = $this->gpm->findPackage($dependencyName); + $this->processPackage($package, $type === 'update'); + } + $io->newLine(); + } elseif ($required) { + throw new Exception(); + } + } + } + + /** + * @param Package|null $package + * @param bool $is_update True if the package is an update + * @return void + */ + private function processPackage(?Package $package, bool $is_update = false): void + { + $io = $this->getIO(); + + if (!$package) { + $io->writeln('Package not found on the GPM!'); + $io->newLine(); + return; + } + + $symlink = false; + if ($this->use_symlinks) { + if (!isset($package->version) || $this->getSymlinkSource($package)) { + $symlink = true; + } + } + + $symlink ? $this->processSymlink($package) : $this->processGpm($package, $is_update); + + $this->processDemo($package); + } + + /** + * Add package to the queue to process the demo content, if demo content exists + * + * @param Package $package + * @return void + */ + private function processDemo(Package $package): void + { + $demo_dir = $this->destination . DS . $package->install_path . DS . '_demo'; + if (file_exists($demo_dir)) { + $this->demo_processing[] = $package; + } + } + + /** + * Prompt to install the demo content of a package + * + * @param Package $package + * @return void + */ + private function installDemoContent(Package $package): void + { + $io = $this->getIO(); + $demo_dir = $this->destination . DS . $package->install_path . DS . '_demo'; + + if (file_exists($demo_dir)) { + $dest_dir = $this->destination . DS . 'user'; + $pages_dir = $dest_dir . DS . 'pages'; + + // Demo content exists, prompt to install it. + $io->writeln("Attention: {$package->name} contains demo content"); + + $question = new ConfirmationQuestion('Do you wish to install this demo content? [y|N] ', false); + + $answer = $io->askQuestion($question); + + if (!$answer) { + $io->writeln(" '- Skipped! "); + $io->newLine(); + + return; + } + + // if pages folder exists in demo + if (file_exists($demo_dir . DS . 'pages')) { + $pages_backup = 'pages.' . date('m-d-Y-H-i-s'); + $question = new ConfirmationQuestion('This will backup your current `user/pages` folder to `user/' . $pages_backup . '`, continue? [y|N]', false); + $answer = $this->all_yes ? true : $io->askQuestion($question); + + if (!$answer) { + $io->writeln(" '- Skipped! "); + $io->newLine(); + + return; + } + + // backup current pages folder + if (file_exists($dest_dir)) { + if (rename($pages_dir, $dest_dir . DS . $pages_backup)) { + $io->writeln(' |- Backing up pages... ok'); + } else { + $io->writeln(' |- Backing up pages... failed'); + } + } + } + + // Confirmation received, copy over the data + $io->writeln(' |- Installing demo content... ok '); + Folder::rcopy($demo_dir, $dest_dir); + $io->writeln(" '- Success! "); + $io->newLine(); + } + } + + /** + * @param Package $package + * @return array|false + */ + private function getGitRegexMatches(Package $package) + { + if (isset($package->repository)) { + $repository = $package->repository; + } else { + return false; + } + + preg_match(GIT_REGEX, $repository, $matches); + + return $matches; + } + + /** + * @param Package $package + * @return string|false + */ + private function getSymlinkSource(Package $package) + { + $matches = $this->getGitRegexMatches($package); + + foreach ($this->local_config as $paths) { + if (Utils::endsWith($matches[2], '.git')) { + $repo_dir = preg_replace('/\.git$/', '', $matches[2]); + } else { + $repo_dir = $matches[2]; + } + + $paths = (array) $paths; + foreach ($paths as $repo) { + $path = rtrim($repo, '/') . '/' . $repo_dir; + if (file_exists($path)) { + return $path; + } + } + } + + return false; + } + + /** + * @param Package $package + * @return void + */ + private function processSymlink(Package $package): void + { + $io = $this->getIO(); + + exec('cd ' . escapeshellarg($this->destination)); + + $to = $this->destination . DS . $package->install_path; + $from = $this->getSymlinkSource($package); + + $io->writeln("Preparing to Symlink {$package->name}"); + $io->write(' |- Checking source... '); + + if (file_exists($from)) { + $io->writeln('ok'); + + $io->write(' |- Checking destination... '); + $checks = $this->checkDestination($package); + + if (!$checks) { + $io->writeln(" '- Installation failed or aborted."); + $io->newLine(); + } elseif (file_exists($to)) { + $io->writeln(" '- Symlink cannot overwrite an existing package, please remove first"); + $io->newLine(); + } else { + symlink($from, $to); + + // extra white spaces to clear out the buffer properly + $io->writeln(' |- Symlinking package... ok '); + $io->writeln(" '- Success! "); + $io->newLine(); + } + + return; + } + + $io->writeln('not found!'); + $io->writeln(" '- Installation failed or aborted."); + } + + /** + * @param Package $package + * @param bool $is_update + * @return bool + */ + private function processGpm(Package $package, bool $is_update = false) + { + $io = $this->getIO(); + + $version = $package->available ?? $package->version; + $license = Licenses::get($package->slug); + + $io->writeln("Preparing to install {$package->name} [v{$version}]"); + + $io->write(' |- Downloading package... 0%'); + $this->file = $this->downloadPackage($package, $license); + + if (!$this->file) { + $io->writeln(" '- Installation failed or aborted."); + $io->newLine(); + + return false; + } + + $io->write(' |- Checking destination... '); + $checks = $this->checkDestination($package); + + if (!$checks) { + $io->writeln(" '- Installation failed or aborted."); + $io->newLine(); + } else { + $io->write(' |- Installing package... '); + $installation = $this->installPackage($package, $is_update); + if (!$installation) { + $io->writeln(" '- Installation failed or aborted."); + $io->newLine(); + } else { + $io->writeln(" '- Success! "); + $io->newLine(); + + return true; + } + } + + return false; + } + + /** + * @param Package $package + * @param string|null $license + * @return string|null + */ + private function downloadPackage(Package $package, string $license = null) + { + $io = $this->getIO(); + + $tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true); + $this->tmp = $tmp_dir . '/Grav-' . uniqid(); + $filename = $package->slug . Utils::basename($package->zipball_url); + $filename = preg_replace('/[\\\\\/:"*?&<>|]+/m', '-', $filename); + $query = ''; + + if (!empty($package->premium)) { + $query = json_encode(array_merge( + $package->premium, + [ + 'slug' => $package->slug, + 'filename' => $package->premium['filename'], + 'license_key' => $license, + 'sid' => md5(GRAV_ROOT) + ] + )); + + $query = '?d=' . base64_encode($query); + } + + try { + $output = Response::get($package->zipball_url . $query, [], [$this, 'progress']); + } catch (Exception $e) { + if (!empty($package->premium) && $e->getCode() === 401) { + $message = 'Unauthorized Premium License Key'; + } else { + $message = $e->getMessage(); + } + + $error = str_replace("\n", "\n | '- ", $message); + $io->write("\x0D"); + // extra white spaces to clear out the buffer properly + $io->writeln(' |- Downloading package... error '); + $io->writeln(" | '- " . $error); + + return null; + } + + Folder::create($this->tmp); + + $io->write("\x0D"); + $io->write(' |- Downloading package... 100%'); + $io->newLine(); + + file_put_contents($this->tmp . DS . $filename, $output); + + return $this->tmp . DS . $filename; + } + + /** + * @param Package $package + * @return bool + */ + private function checkDestination(Package $package): bool + { + $io = $this->getIO(); + + Installer::isValidDestination($this->destination . DS . $package->install_path); + + if (Installer::lastErrorCode() === Installer::IS_LINK) { + $io->write("\x0D"); + $io->writeln(' |- Checking destination... symbolic link'); + + if ($this->all_yes) { + $io->writeln(" | '- Skipped automatically."); + + return false; + } + + $question = new ConfirmationQuestion( + " | '- Destination has been detected as symlink, delete symbolic link first? [y|N] ", + false + ); + $answer = $io->askQuestion($question); + + if (!$answer) { + $io->writeln(" | '- You decided to not delete the symlink automatically."); + + return false; + } + + unlink($this->destination . DS . $package->install_path); + } + + $io->write("\x0D"); + $io->writeln(' |- Checking destination... ok'); + + return true; + } + + /** + * Install a package + * + * @param Package $package + * @param bool $is_update True if it's an update. False if it's an install + * @return bool + */ + private function installPackage(Package $package, bool $is_update = false): bool + { + $io = $this->getIO(); + + $type = $package->package_type; + + Installer::install($this->file, $this->destination, ['install_path' => $package->install_path, 'theme' => $type === 'themes', 'is_update' => $is_update]); + $error_code = Installer::lastErrorCode(); + Folder::delete($this->tmp); + + if ($error_code) { + $io->write("\x0D"); + // extra white spaces to clear out the buffer properly + $io->writeln(' |- Installing package... error '); + $io->writeln(" | '- " . Installer::lastErrorMsg()); + + return false; + } + + $message = Installer::getMessage(); + if ($message) { + $io->write("\x0D"); + // extra white spaces to clear out the buffer properly + $io->writeln(" |- {$message}"); + } + + $io->write("\x0D"); + // extra white spaces to clear out the buffer properly + $io->writeln(' |- Installing package... ok '); + + return true; + } + + /** + * @param array $progress + * @return void + */ + public function progress(array $progress): void + { + $io = $this->getIO(); + + $io->write("\x0D"); + $io->write(' |- Downloading package... ' . str_pad( + $progress['percent'], + 5, + ' ', + STR_PAD_LEFT + ) . '%'); + } +} diff --git a/system/src/Grav/Console/Gpm/SelfupgradeCommand.php b/system/src/Grav/Console/Gpm/SelfupgradeCommand.php new file mode 100644 index 0000000..a3c7c6f --- /dev/null +++ b/system/src/Grav/Console/Gpm/SelfupgradeCommand.php @@ -0,0 +1,344 @@ +setName('self-upgrade') + ->setAliases(['selfupgrade', 'selfupdate']) + ->addOption( + 'force', + 'f', + InputOption::VALUE_NONE, + 'Force re-fetching the data from remote' + ) + ->addOption( + 'all-yes', + 'y', + InputOption::VALUE_NONE, + 'Assumes yes (or best approach) instead of prompting' + ) + ->addOption( + 'overwrite', + 'o', + InputOption::VALUE_NONE, + 'Option to overwrite packages if they already exist' + ) + ->addOption( + 'timeout', + 't', + InputOption::VALUE_OPTIONAL, + 'Option to set the timeout in seconds when downloading the update (0 for no timeout)', + 30 + ) + ->setDescription('Detects and performs an update of Grav itself when available') + ->setHelp('The update command updates Grav itself when a new version is available'); + } + + /** + * @return int + */ + protected function serve(): int + { + $input = $this->getInput(); + $io = $this->getIO(); + + if (!class_exists(ZipArchive::class)) { + $io->title('GPM Self Upgrade'); + $io->error('php-zip extension needs to be enabled!'); + + return 1; + } + + $this->upgrader = new Upgrader($input->getOption('force')); + $this->all_yes = $input->getOption('all-yes'); + $this->overwrite = $input->getOption('overwrite'); + $this->timeout = (int) $input->getOption('timeout'); + + $this->displayGPMRelease(); + + $update = $this->upgrader->getAssets()['grav-update']; + + $local = $this->upgrader->getLocalVersion(); + $remote = $this->upgrader->getRemoteVersion(); + $release = strftime('%c', strtotime($this->upgrader->getReleaseDate())); + + if (!$this->upgrader->meetsRequirements()) { + $io->writeln('ATTENTION:'); + $io->writeln(' Grav has increased the minimum PHP requirement.'); + $io->writeln(' You are currently running PHP ' . phpversion() . ', but PHP ' . $this->upgrader->minPHPVersion() . ' is required.'); + $io->writeln(' Additional information: http://getgrav.org/blog/changing-php-requirements'); + $io->newLine(); + $io->writeln('Selfupgrade aborted.'); + $io->newLine(); + + return 1; + } + + if (!$this->overwrite && !$this->upgrader->isUpgradable()) { + $io->writeln("You are already running the latest version of Grav v{$local}"); + $io->writeln("which was released on {$release}"); + + $config = Grav::instance()['config']; + $schema = $config->get('versions.core.grav.schema'); + if ($schema !== GRAV_SCHEMA && version_compare($schema, GRAV_SCHEMA, '<')) { + $io->newLine(); + $io->writeln('However post-install scripts have not been run.'); + if (!$this->all_yes) { + $question = new ConfirmationQuestion( + 'Would you like to run the scripts? [Y|n] ', + true + ); + $answer = $io->askQuestion($question); + } else { + $answer = true; + } + + if ($answer) { + // Finalize installation. + Install::instance()->finalize(); + + $io->write(' |- Running post-install scripts... '); + $io->writeln(" '- Success! "); + $io->newLine(); + } + } + + return 0; + } + + Installer::isValidDestination(GRAV_ROOT . '/system'); + if (Installer::IS_LINK === Installer::lastErrorCode()) { + $io->writeln('ATTENTION: Grav is symlinked, cannot upgrade, aborting...'); + $io->newLine(); + $io->writeln("You are currently running a symbolically linked Grav v{$local}. Latest available is v{$remote}."); + + return 1; + } + + // not used but preloaded just in case! + new ArrayInput([]); + + $io->writeln("Grav v{$remote} is now available [release date: {$release}]."); + $io->writeln('You are currently using v' . GRAV_VERSION . '.'); + + if (!$this->all_yes) { + $question = new ConfirmationQuestion( + 'Would you like to read the changelog before proceeding? [y|N] ', + false + ); + $answer = $io->askQuestion($question); + + if ($answer) { + $changelog = $this->upgrader->getChangelog(GRAV_VERSION); + + $io->newLine(); + foreach ($changelog as $version => $log) { + $title = $version . ' [' . $log['date'] . ']'; + $content = preg_replace_callback('/\d\.\s\[\]\(#(.*)\)/', static function ($match) { + return "\n" . ucfirst($match[1]) . ':'; + }, $log['content']); + + $io->writeln($title); + $io->writeln(str_repeat('-', strlen($title))); + $io->writeln($content); + $io->newLine(); + } + + $question = new ConfirmationQuestion('Press [ENTER] to continue.', true); + $io->askQuestion($question); + } + + $question = new ConfirmationQuestion('Would you like to upgrade now? [y|N] ', false); + $answer = $io->askQuestion($question); + + if (!$answer) { + $io->writeln('Aborting...'); + + return 1; + } + } + + $io->newLine(); + $io->writeln("Preparing to upgrade to v{$remote}.."); + + $io->write(" |- Downloading upgrade [{$this->formatBytes($update['size'])}]... 0%"); + $this->file = $this->download($update); + + $io->write(' |- Installing upgrade... '); + $installation = $this->upgrade(); + + $error = 0; + if (!$installation) { + $io->writeln(" '- Installation failed or aborted."); + $io->newLine(); + $error = 1; + } else { + $io->writeln(" '- Success! "); + $io->newLine(); + } + + if ($this->tmp && is_dir($this->tmp)) { + Folder::delete($this->tmp); + } + + return $error; + } + + /** + * @param array $package + * @return string + */ + private function download(array $package): string + { + $io = $this->getIO(); + + $tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true); + $this->tmp = $tmp_dir . '/grav-update-' . uniqid('', false); + $options = [ + 'timeout' => $this->timeout, + ]; + + $output = Response::get($package['download'], $options, [$this, 'progress']); + + Folder::create($this->tmp); + + $io->write("\x0D"); + $io->write(" |- Downloading upgrade [{$this->formatBytes($package['size'])}]... 100%"); + $io->newLine(); + + file_put_contents($this->tmp . DS . $package['name'], $output); + + return $this->tmp . DS . $package['name']; + } + + /** + * @return bool + */ + private function upgrade(): bool + { + $io = $this->getIO(); + + $this->upgradeGrav($this->file); + + $errorCode = Installer::lastErrorCode(); + if ($errorCode) { + $io->write("\x0D"); + // extra white spaces to clear out the buffer properly + $io->writeln(' |- Installing upgrade... error '); + $io->writeln(" | '- " . Installer::lastErrorMsg()); + + return false; + } + + $io->write("\x0D"); + // extra white spaces to clear out the buffer properly + $io->writeln(' |- Installing upgrade... ok '); + + return true; + } + + /** + * @param array $progress + * @return void + */ + public function progress(array $progress): void + { + $io = $this->getIO(); + + $io->write("\x0D"); + $io->write(" |- Downloading upgrade [{$this->formatBytes($progress['filesize']) }]... " . str_pad( + $progress['percent'], + 5, + ' ', + STR_PAD_LEFT + ) . '%'); + } + + /** + * @param int|float $size + * @param int $precision + * @return string + */ + public function formatBytes($size, int $precision = 2): string + { + $base = log($size) / log(1024); + $suffixes = array('', 'k', 'M', 'G', 'T'); + + return round(1024 ** ($base - floor($base)), $precision) . $suffixes[(int)floor($base)]; + } + + /** + * @param string $zip + * @return void + */ + private function upgradeGrav(string $zip): void + { + try { + $folder = Installer::unZip($zip, $this->tmp . '/zip'); + if ($folder === false) { + throw new RuntimeException(Installer::lastErrorMsg()); + } + + $script = $folder . '/system/install.php'; + if ((file_exists($script) && $install = include $script) && is_callable($install)) { + $install($zip); + } else { + throw new RuntimeException('Uploaded archive file is not a valid Grav update package'); + } + } catch (Exception $e) { + Installer::setError($e->getMessage()); + } + } +} diff --git a/system/src/Grav/Console/Gpm/UninstallCommand.php b/system/src/Grav/Console/Gpm/UninstallCommand.php new file mode 100644 index 0000000..628e404 --- /dev/null +++ b/system/src/Grav/Console/Gpm/UninstallCommand.php @@ -0,0 +1,312 @@ +setName('uninstall') + ->addOption( + 'all-yes', + 'y', + InputOption::VALUE_NONE, + 'Assumes yes (or best approach) instead of prompting' + ) + ->addArgument( + 'package', + InputArgument::IS_ARRAY | InputArgument::REQUIRED, + 'The package(s) that are desired to be removed. Use the "index" command for a list of packages' + ) + ->setDescription('Performs the uninstallation of plugins and themes') + ->setHelp('The uninstall command allows to uninstall plugins and themes'); + } + + /** + * @return int + */ + protected function serve(): int + { + $input = $this->getInput(); + $io = $this->getIO(); + + $this->gpm = new GPM(); + + $this->all_yes = $input->getOption('all-yes'); + + $packages = array_map('strtolower', $input->getArgument('package')); + $this->data = ['total' => 0, 'not_found' => []]; + + $total = 0; + foreach ($packages as $package) { + $plugin = $this->gpm->getInstalledPlugin($package); + $theme = $this->gpm->getInstalledTheme($package); + if ($plugin || $theme) { + $this->data[strtolower($package)] = $plugin ?: $theme; + $total++; + } else { + $this->data['not_found'][] = $package; + } + } + $this->data['total'] = $total; + + $io->newLine(); + + if (!$this->data['total']) { + $io->writeln('Nothing to uninstall.'); + $io->newLine(); + + return 0; + } + + if (count($this->data['not_found'])) { + $io->writeln('These packages were not found installed: ' . implode( + ', ', + $this->data['not_found'] + ) . ''); + } + + unset($this->data['not_found'], $this->data['total']); + + // Plugins need to be initialized in order to make clearcache to work. + try { + $this->initializePlugins(); + } catch (Throwable $e) { + $io->writeln("Some plugins failed to initialize: {$e->getMessage()}"); + } + + $error = 0; + foreach ($this->data as $slug => $package) { + $io->writeln("Preparing to uninstall {$package->name} [v{$package->version}]"); + + $io->write(' |- Checking destination... '); + $checks = $this->checkDestination($slug, $package); + + if (!$checks) { + $io->writeln(" '- Installation failed or aborted."); + $io->newLine(); + $error = 1; + } else { + $uninstall = $this->uninstallPackage($slug, $package); + + if (!$uninstall) { + $io->writeln(" '- Uninstallation failed or aborted."); + $error = 1; + } else { + $io->writeln(" '- Success! "); + } + } + } + + // clear cache after successful upgrade + $this->clearCache(); + + return $error; + } + + /** + * @param string $slug + * @param Local\Package|Remote\Package $package + * @param bool $is_dependency + * @return bool + */ + private function uninstallPackage($slug, $package, $is_dependency = false): bool + { + $io = $this->getIO(); + + if (!$slug) { + return false; + } + + //check if there are packages that have this as a dependency. Abort and show list + $dependent_packages = $this->gpm->getPackagesThatDependOnPackage($slug); + if (count($dependent_packages) > ($is_dependency ? 1 : 0)) { + $io->newLine(2); + $io->writeln('Uninstallation failed.'); + $io->newLine(); + if (count($dependent_packages) > ($is_dependency ? 2 : 1)) { + $io->writeln('The installed packages ' . implode(', ', $dependent_packages) . ' depends on this package. Please remove those first.'); + } else { + $io->writeln('The installed package ' . implode(', ', $dependent_packages) . ' depends on this package. Please remove it first.'); + } + + $io->newLine(); + return false; + } + + if (isset($package->dependencies)) { + $dependencies = $package->dependencies; + + if ($is_dependency) { + foreach ($dependencies as $key => $dependency) { + if (in_array($dependency['name'], $this->dependencies, true)) { + unset($dependencies[$key]); + } + } + } elseif (count($dependencies) > 0) { + $io->writeln(' `- Dependencies found...'); + $io->newLine(); + } + + foreach ($dependencies as $dependency) { + $this->dependencies[] = $dependency['name']; + + if (is_array($dependency)) { + $dependency = $dependency['name']; + } + if ($dependency === 'grav' || $dependency === 'php') { + continue; + } + + $dependencyPackage = $this->gpm->findPackage($dependency); + + $dependency_exists = $this->packageExists($dependency, $dependencyPackage); + + if ($dependency_exists == Installer::EXISTS) { + $io->writeln("A dependency on {$dependencyPackage->name} [v{$dependencyPackage->version}] was found"); + + $question = new ConfirmationQuestion(" |- Uninstall {$dependencyPackage->name}? [y|N] ", false); + $answer = $this->all_yes ? true : $io->askQuestion($question); + + if ($answer) { + $uninstall = $this->uninstallPackage($dependency, $dependencyPackage, true); + + if (!$uninstall) { + $io->writeln(" '- Uninstallation failed or aborted."); + } else { + $io->writeln(" '- Success! "); + } + $io->newLine(); + } else { + $io->writeln(" '- You decided not to uninstall {$dependencyPackage->name}."); + $io->newLine(); + } + } + } + } + + + $locator = Grav::instance()['locator']; + $path = $locator->findResource($package->package_type . '://' . $slug); + Installer::uninstall($path); + $errorCode = Installer::lastErrorCode(); + + if ($errorCode && $errorCode !== Installer::IS_LINK && $errorCode !== Installer::EXISTS) { + $io->writeln(" |- Uninstalling {$package->name} package... error "); + $io->writeln(" | '- " . Installer::lastErrorMsg() . ''); + + return false; + } + + $message = Installer::getMessage(); + if ($message) { + $io->writeln(" |- {$message}"); + } + + if (!$is_dependency && $this->dependencies) { + $io->writeln("Finishing up uninstalling {$package->name}"); + } + $io->writeln(" |- Uninstalling {$package->name} package... ok "); + + return true; + } + + /** + * @param string $slug + * @param Local\Package|Remote\Package $package + * @return bool + */ + private function checkDestination(string $slug, $package): bool + { + $io = $this->getIO(); + + $exists = $this->packageExists($slug, $package); + + if ($exists === Installer::IS_LINK) { + $io->write("\x0D"); + $io->writeln(' |- Checking destination... symbolic link'); + + if ($this->all_yes) { + $io->writeln(" | '- Skipped automatically."); + + return false; + } + + $question = new ConfirmationQuestion( + " | '- Destination has been detected as symlink, delete symbolic link first? [y|N] ", + false + ); + + $answer = $io->askQuestion($question); + if (!$answer) { + $io->writeln(" | '- You decided not to delete the symlink automatically."); + + return false; + } + } + + $io->write("\x0D"); + $io->writeln(' |- Checking destination... ok'); + + return true; + } + + /** + * Check if package exists + * + * @param string $slug + * @param Local\Package|Remote\Package $package + * @return int + */ + private function packageExists(string $slug, $package): int + { + $path = Grav::instance()['locator']->findResource($package->package_type . '://' . $slug); + Installer::isValidDestination($path); + + return Installer::lastErrorCode(); + } +} diff --git a/system/src/Grav/Console/Gpm/UpdateCommand.php b/system/src/Grav/Console/Gpm/UpdateCommand.php new file mode 100644 index 0000000..6e20c93 --- /dev/null +++ b/system/src/Grav/Console/Gpm/UpdateCommand.php @@ -0,0 +1,289 @@ +setName('update') + ->addOption( + 'force', + 'f', + InputOption::VALUE_NONE, + 'Force re-fetching the data from remote' + ) + ->addOption( + 'destination', + 'd', + InputOption::VALUE_OPTIONAL, + 'The grav instance location where the updates should be applied to. By default this would be where the grav cli has been launched from', + GRAV_ROOT + ) + ->addOption( + 'all-yes', + 'y', + InputOption::VALUE_NONE, + 'Assumes yes (or best approach) instead of prompting' + ) + ->addOption( + 'overwrite', + 'o', + InputOption::VALUE_NONE, + 'Option to overwrite packages if they already exist' + ) + ->addOption( + 'plugins', + 'p', + InputOption::VALUE_NONE, + 'Update only plugins' + ) + ->addOption( + 'themes', + 't', + InputOption::VALUE_NONE, + 'Update only themes' + ) + ->addArgument( + 'package', + InputArgument::IS_ARRAY | InputArgument::OPTIONAL, + 'The package or packages that is desired to update. By default all available updates will be applied.' + ) + ->setDescription('Detects and performs an update of plugins and themes when available') + ->setHelp('The update command updates plugins and themes when a new version is available'); + } + + /** + * @return int + */ + protected function serve(): int + { + $input = $this->getInput(); + $io = $this->getIO(); + + if (!class_exists(ZipArchive::class)) { + $io->title('GPM Update'); + $io->error('php-zip extension needs to be enabled!'); + + return 1; + } + + $this->upgrader = new Upgrader($input->getOption('force')); + $local = $this->upgrader->getLocalVersion(); + $remote = $this->upgrader->getRemoteVersion(); + if ($local !== $remote) { + $io->writeln('WARNING: A new version of Grav is available. You should update Grav before updating plugins and themes. If you continue without updating Grav, some plugins or themes may stop working.'); + $io->newLine(); + $question = new ConfirmationQuestion('Continue with the update process? [Y|n] ', true); + $answer = $io->askQuestion($question); + + if (!$answer) { + $io->writeln('Update aborted. Exiting...'); + + return 1; + } + } + + $this->gpm = new GPM($input->getOption('force')); + + $this->all_yes = $input->getOption('all-yes'); + $this->overwrite = $input->getOption('overwrite'); + + $this->displayGPMRelease(); + + $this->destination = realpath($input->getOption('destination')); + + if (!Installer::isGravInstance($this->destination)) { + $io->writeln('ERROR: ' . Installer::lastErrorMsg()); + exit; + } + if ($input->getOption('plugins') === false && $input->getOption('themes') === false) { + $list_type = ['plugins' => true, 'themes' => true]; + } else { + $list_type['plugins'] = $input->getOption('plugins'); + $list_type['themes'] = $input->getOption('themes'); + } + + if ($this->overwrite) { + $this->data = $this->gpm->getInstallable($list_type); + $description = ' can be overwritten'; + } else { + $this->data = $this->gpm->getUpdatable($list_type); + $description = ' need updating'; + } + + $only_packages = array_map('strtolower', $input->getArgument('package')); + + if (!$this->overwrite && !$this->data['total']) { + $io->writeln('Nothing to update.'); + + return 0; + } + + $io->write("Found {$this->gpm->countInstalled()} packages installed of which {$this->data['total']}{$description}"); + + $limit_to = $this->userInputPackages($only_packages); + + $io->newLine(); + + unset($this->data['total'], $limit_to['total']); + + + // updates review + $slugs = []; + + $index = 1; + foreach ($this->data as $packages) { + foreach ($packages as $slug => $package) { + if (!array_key_exists($slug, $limit_to) && count($only_packages)) { + continue; + } + + if (!$package->available) { + $package->available = $package->version; + } + + $io->writeln( + // index + str_pad((string)$index++, 2, '0', STR_PAD_LEFT) . '. ' . + // name + '' . str_pad($package->name, 15) . ' ' . + // version + "[v{$package->version} -> v{$package->available}]" + ); + $slugs[] = $slug; + } + } + + if (!$this->all_yes) { + // prompt to continue + $io->newLine(); + $question = new ConfirmationQuestion('Continue with the update process? [Y|n] ', true); + $answer = $io->askQuestion($question); + + if (!$answer) { + $io->writeln('Update aborted. Exiting...'); + + return 1; + } + } + + // finally update + $install_command = $this->getApplication()->find('install'); + + $args = new ArrayInput([ + 'command' => 'install', + 'package' => $slugs, + '-f' => $input->getOption('force'), + '-d' => $this->destination, + '-y' => true + ]); + $command_exec = $install_command->run($args, $io); + + if ($command_exec != 0) { + $io->writeln('Error: An error occurred while trying to install the packages'); + + return 1; + } + + return 0; + } + + /** + * @param array $only_packages + * @return array + */ + private function userInputPackages(array $only_packages): array + { + $io = $this->getIO(); + + $found = ['total' => 0]; + $ignore = []; + + if (!count($only_packages)) { + $io->newLine(); + } else { + foreach ($only_packages as $only_package) { + $find = $this->gpm->findPackage($only_package); + + if (!$find || (!$this->overwrite && !$this->gpm->isUpdatable($find->slug))) { + $name = $find->slug ?? $only_package; + $ignore[$name] = $name; + } else { + $found[$find->slug] = $find; + $found['total']++; + } + } + + if ($found['total']) { + $list = $found; + unset($list['total']); + $list = array_keys($list); + + if ($found['total'] !== $this->data['total']) { + $io->write(", only {$found['total']} will be updated"); + } + + $io->newLine(); + $io->writeln('Limiting updates for only ' . implode( + ', ', + $list + ) . ''); + } + + if (count($ignore)) { + $io->newLine(); + $io->writeln('Packages not found or not requiring updates: ' . implode( + ', ', + $ignore + ) . ''); + } + } + + return $found; + } +} diff --git a/system/src/Grav/Console/Gpm/VersionCommand.php b/system/src/Grav/Console/Gpm/VersionCommand.php new file mode 100644 index 0000000..76be6bb --- /dev/null +++ b/system/src/Grav/Console/Gpm/VersionCommand.php @@ -0,0 +1,125 @@ +setName('version') + ->addOption( + 'force', + 'f', + InputOption::VALUE_NONE, + 'Force re-fetching the data from remote' + ) + ->addArgument( + 'package', + InputArgument::IS_ARRAY | InputArgument::OPTIONAL, + 'The package or packages that is desired to know the version of. By default and if not specified this would be grav' + ) + ->setDescription('Shows the version of an installed package. If available also shows pending updates.') + ->setHelp('The version command displays the current version of a package installed and, if available, the available version of pending updates'); + } + + /** + * @return int + */ + protected function serve(): int + { + $input = $this->getInput(); + $io = $this->getIO(); + + $this->gpm = new GPM($input->getOption('force')); + $packages = $input->getArgument('package'); + + $installed = false; + + if (!count($packages)) { + $packages = ['grav']; + } + + foreach ($packages as $package) { + $package = strtolower($package); + $name = null; + $version = null; + $updatable = false; + + if ($package === 'grav') { + $name = 'Grav'; + $version = GRAV_VERSION; + $upgrader = new Upgrader(); + + if ($upgrader->isUpgradable()) { + $updatable = " [upgradable: v{$upgrader->getRemoteVersion()}]"; + } + } else { + // get currently installed version + $locator = Grav::instance()['locator']; + $blueprints_path = $locator->findResource('plugins://' . $package . DS . 'blueprints.yaml'); + if (!file_exists($blueprints_path)) { // theme? + $blueprints_path = $locator->findResource('themes://' . $package . DS . 'blueprints.yaml'); + if (!file_exists($blueprints_path)) { + continue; + } + } + + $file = YamlFile::instance($blueprints_path); + $package_yaml = $file->content(); + $file->free(); + + $version = $package_yaml['version']; + + if (!$version) { + continue; + } + + $installed = $this->gpm->findPackage($package); + if ($installed) { + $name = $installed->name; + + if ($this->gpm->isUpdatable($package)) { + $updatable = " [updatable: v{$installed->available}]"; + } + } + } + + $updatable = $updatable ?: ''; + + if ($installed || $package === 'grav') { + $io->writeln("You are running {$name} v{$version}{$updatable}"); + } else { + $io->writeln("Package {$package} not found"); + } + } + + return 0; + } +} diff --git a/system/src/Grav/Console/GpmCommand.php b/system/src/Grav/Console/GpmCommand.php new file mode 100644 index 0000000..62ab950 --- /dev/null +++ b/system/src/Grav/Console/GpmCommand.php @@ -0,0 +1,68 @@ +setupConsole($input, $output); + + $grav = Grav::instance(); + $grav['config']->init(); + $grav['uri']->init(); + // @phpstan-ignore-next-line + $grav['accounts']; + + return $this->serve(); + } + + /** + * Override with your implementation. + * + * @return int + */ + protected function serve() + { + // Return error. + return 1; + } + + /** + * @return void + */ + protected function displayGPMRelease() + { + /** @var Config $config */ + $config = Grav::instance()['config']; + + $io = $this->getIO(); + $io->newLine(); + $io->writeln('GPM Releases Configuration: ' . ucfirst($config->get('system.gpm.releases')) . ''); + $io->newLine(); + } +} diff --git a/system/src/Grav/Console/GravCommand.php b/system/src/Grav/Console/GravCommand.php new file mode 100644 index 0000000..791a5f3 --- /dev/null +++ b/system/src/Grav/Console/GravCommand.php @@ -0,0 +1,52 @@ +setupConsole($input, $output); + + // Old versions of Grav called this command after grav upgrade. + // We need make this command to work with older ConsoleTrait: + if (method_exists($this, 'initializeGrav')) { + $this->initializeGrav(); + } + + return $this->serve(); + } + + /** + * Override with your implementation. + * + * @return int + */ + protected function serve() + { + // Return error. + return 1; + } +} diff --git a/system/src/Grav/Console/Plugin/PluginListCommand.php b/system/src/Grav/Console/Plugin/PluginListCommand.php new file mode 100644 index 0000000..7052201 --- /dev/null +++ b/system/src/Grav/Console/Plugin/PluginListCommand.php @@ -0,0 +1,69 @@ +setHidden(true); + } + + /** + * @return int + */ + protected function serve(): int + { + $bin = $this->argv; + $pattern = '([A-Z]\w+Command\.php)'; + + $io = $this->getIO(); + $io->newLine(); + $io->writeln('Usage:'); + $io->writeln(" {$bin} [slug] [command] [arguments]"); + $io->newLine(); + $io->writeln('Example:'); + $io->writeln(" {$bin} error log -l 1 --trace"); + $io->newLine(); + $io->writeln('Plugins with CLI available:'); + + $plugins = Plugins::all(); + $index = 0; + foreach ($plugins as $name => $plugin) { + if (!$plugin->enabled) { + continue; + } + + $list = Folder::all("plugins://{$name}", ['compare' => 'Pathname', 'pattern' => '/\/cli\/' . $pattern . '$/usm', 'levels' => 1]); + if (!$list) { + continue; + } + + $index++; + $num = str_pad((string)$index, 2, '0', STR_PAD_LEFT); + $io->writeln(' ' . $num . '. ' . str_pad($name, 15) . " {$bin} {$name} list"); + } + + return 0; + } +} diff --git a/system/src/Grav/Console/TerminalObjects/Table.php b/system/src/Grav/Console/TerminalObjects/Table.php new file mode 100644 index 0000000..83b4be2 --- /dev/null +++ b/system/src/Grav/Console/TerminalObjects/Table.php @@ -0,0 +1,38 @@ +column_widths = $this->getColumnWidths(); + $this->table_width = $this->getWidth(); + $this->border = $this->getBorder(); + + $this->buildHeaderRow(); + + foreach ($this->data as $key => $columns) { + $this->rows[] = $this->buildRow($columns); + } + + $this->rows[] = $this->border; + + return $this->rows; + } +} diff --git a/system/src/Grav/Events/BeforeSessionStartEvent.php b/system/src/Grav/Events/BeforeSessionStartEvent.php new file mode 100644 index 0000000..0dd4753 --- /dev/null +++ b/system/src/Grav/Events/BeforeSessionStartEvent.php @@ -0,0 +1,36 @@ +start() right before session_start() call. + * + * @property SessionInterface $session Session instance. + */ +class BeforeSessionStartEvent extends Event +{ + /** @var SessionInterface */ + public $session; + + public function __construct(SessionInterface $session) + { + $this->session = $session; + } + + public function __debugInfo(): array + { + return (array)$this; + } +} diff --git a/system/src/Grav/Events/FlexRegisterEvent.php b/system/src/Grav/Events/FlexRegisterEvent.php new file mode 100644 index 0000000..4de7252 --- /dev/null +++ b/system/src/Grav/Events/FlexRegisterEvent.php @@ -0,0 +1,45 @@ +flex = $flex; + } + + /** + * @return array + */ + public function __debugInfo(): array + { + return (array)$this; + } +} diff --git a/system/src/Grav/Events/PageEvent.php b/system/src/Grav/Events/PageEvent.php new file mode 100644 index 0000000..1928b0d --- /dev/null +++ b/system/src/Grav/Events/PageEvent.php @@ -0,0 +1,18 @@ +permissions = $permissions; + } + + /** + * @return array + */ + public function __debugInfo(): array + { + return (array)$this; + } +} diff --git a/system/src/Grav/Events/PluginsLoadedEvent.php b/system/src/Grav/Events/PluginsLoadedEvent.php new file mode 100644 index 0000000..6729b39 --- /dev/null +++ b/system/src/Grav/Events/PluginsLoadedEvent.php @@ -0,0 +1,53 @@ +grav = $grav; + $this->plugins = $plugins; + } + + /** + * @return array + */ + public function __debugInfo(): array + { + return [ + 'plugins' => $this->plugins + ]; + } +} diff --git a/system/src/Grav/Events/SessionStartEvent.php b/system/src/Grav/Events/SessionStartEvent.php new file mode 100644 index 0000000..63a4908 --- /dev/null +++ b/system/src/Grav/Events/SessionStartEvent.php @@ -0,0 +1,36 @@ +start() right after successful session_start() call. + * + * @property SessionInterface $session Session instance. + */ +class SessionStartEvent extends Event +{ + /** @var SessionInterface */ + public $session; + + public function __construct(SessionInterface $session) + { + $this->session = $session; + } + + public function __debugInfo(): array + { + return (array)$this; + } +} diff --git a/system/src/Grav/Events/TypesEvent.php b/system/src/Grav/Events/TypesEvent.php new file mode 100644 index 0000000..a2b39a4 --- /dev/null +++ b/system/src/Grav/Events/TypesEvent.php @@ -0,0 +1,18 @@ + + */ +class Access implements JsonSerializable, IteratorAggregate, Countable +{ + /** @var string */ + private $name; + /** @var array */ + private $rules; + /** @var array */ + private $ops; + /** @var array */ + private $acl = []; + /** @var array */ + private $inherited = []; + + /** + * Access constructor. + * @param string|array|null $acl + * @param array|null $rules + * @param string $name + */ + public function __construct($acl = null, array $rules = null, string $name = '') + { + $this->name = $name; + $this->rules = $rules ?? []; + $this->ops = ['+' => true, '-' => false]; + if (is_string($acl)) { + $this->acl = $this->resolvePermissions($acl); + } elseif (is_array($acl)) { + $this->acl = $this->normalizeAcl($acl); + } + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @param Access $parent + * @param string|null $name + * @return void + */ + public function inherit(Access $parent, string $name = null) + { + // Remove cached null actions from acl. + $acl = $this->getAllActions(); + // Get only inherited actions. + $inherited = array_diff_key($parent->getAllActions(), $acl); + + $this->inherited += $parent->inherited + array_fill_keys(array_keys($inherited), $name ?? $parent->getName()); + $this->acl = array_replace($acl, $inherited); + } + + /** + * Checks user authorization to the action. + * + * @param string $action + * @param string|null $scope + * @return bool|null + */ + public function authorize(string $action, string $scope = null): ?bool + { + if (null !== $scope) { + $action = $scope !== 'test' ? "{$scope}.{$action}" : $action; + } + + return $this->get($action); + } + + /** + * @return array + */ + public function toArray(): array + { + return Utils::arrayUnflattenDotNotation($this->acl); + } + + /** + * @return array + */ + public function getAllActions(): array + { + return array_filter($this->acl, static function($val) { return $val !== null; }); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return $this->toArray(); + } + + /** + * @param string $action + * @return bool|null + */ + public function get(string $action) + { + // Get access value. + if (isset($this->acl[$action])) { + return $this->acl[$action]; + } + + // If no value is defined, check the parent access (all true|false). + $pos = strrpos($action, '.'); + $value = $pos ? $this->get(substr($action, 0, $pos)) : null; + + // Cache result for faster lookup. + $this->acl[$action] = $value; + + return $value; + } + + /** + * @param string $action + * @return bool + */ + public function isInherited(string $action): bool + { + return isset($this->inherited[$action]); + } + + /** + * @param string $action + * @return string|null + */ + public function getInherited(string $action): ?string + { + return $this->inherited[$action] ?? null; + } + + /** + * @return Traversable + */ + public function getIterator(): Traversable + { + return new ArrayIterator($this->acl); + } + + /** + * @return int + */ + public function count(): int + { + return count($this->acl); + } + + /** + * @param array $acl + * @return array + */ + protected function normalizeAcl(array $acl): array + { + if (empty($acl)) { + return []; + } + + // Normalize access control list. + $list = []; + foreach (Utils::arrayFlattenDotNotation($acl) as $key => $value) { + if (is_bool($value)) { + $list[$key] = $value; + } elseif ($value === 0 || $value === 1) { + $list[$key] = (bool)$value; + } elseif($value === null) { + continue; + } elseif ($this->rules && is_string($value)) { + $list[$key] = $this->resolvePermissions($value); + } elseif (Utils::isPositive($value)) { + $list[$key] = true; + } elseif (Utils::isNegative($value)) { + $list[$key] = false; + } + } + + return $list; + } + + /** + * @param string $access + * @return array + */ + protected function resolvePermissions(string $access): array + { + $len = strlen($access); + $op = true; + $list = []; + for($count = 0; $count < $len; $count++) { + $letter = $access[$count]; + if (isset($this->rules[$letter])) { + $list[$this->rules[$letter]] = $op; + $op = true; + } elseif (isset($this->ops[$letter])) { + $op = $this->ops[$letter]; + } + } + + return $list; + } +} diff --git a/system/src/Grav/Framework/Acl/Action.php b/system/src/Grav/Framework/Acl/Action.php new file mode 100644 index 0000000..5ea8f84 --- /dev/null +++ b/system/src/Grav/Framework/Acl/Action.php @@ -0,0 +1,204 @@ + + */ +class Action implements IteratorAggregate, Countable +{ + /** @var string */ + public $name; + /** @var string */ + public $type; + /** @var bool */ + public $visible; + /** @var string|null */ + public $label; + /** @var array */ + public $params; + + /** @var Action|null */ + protected $parent; + /** @var array */ + protected $children = []; + + /** + * @param string $name + * @param array $action + */ + public function __construct(string $name, array $action = []) + { + $label = $action['label'] ?? null; + if (!$label) { + if ($pos = mb_strrpos($name, '.')) { + $label = mb_substr($name, $pos + 1); + } else { + $label = $name; + } + $label = Inflector::humanize($label, 'all'); + } + + $this->name = $name; + $this->type = $action['type'] ?? 'action'; + $this->visible = (bool)($action['visible'] ?? true); + $this->label = $label; + unset($action['type'], $action['label']); + $this->params = $action; + + // Include compact rules. + if (isset($action['letters'])) { + foreach ($action['letters'] as $letter => $data) { + $data['letter'] = $letter; + $childName = $this->name . '.' . $data['action']; + unset($data['action']); + $child = new Action($childName, $data); + $this->addChild($child); + } + } + } + + /** + * @return array + */ + public function getParams(): array + { + return $this->params; + } + + /** + * @param string $name + * @return mixed|null + */ + public function getParam(string $name) + { + return $this->params[$name] ?? null; + } + + /** + * @return Action|null + */ + public function getParent(): ?Action + { + return $this->parent; + } + + /** + * @param Action|null $parent + * @return void + */ + public function setParent(?Action $parent): void + { + $this->parent = $parent; + } + + /** + * @return string + */ + public function getScope(): string + { + $pos = mb_strpos($this->name, '.'); + if ($pos) { + return mb_substr($this->name, 0, $pos); + } + + return $this->name; + } + + /** + * @return int + */ + public function getLevels(): int + { + return mb_substr_count($this->name, '.'); + } + + /** + * @return bool + */ + public function hasChildren(): bool + { + return !empty($this->children); + } + + /** + * @return Action[] + */ + public function getChildren(): array + { + return $this->children; + } + + /** + * @param string $name + * @return Action|null + */ + public function getChild(string $name): ?Action + { + return $this->children[$name] ?? null; + } + + /** + * @param Action $child + * @return void + */ + public function addChild(Action $child): void + { + if (mb_strpos($child->name, "{$this->name}.") !== 0) { + throw new RuntimeException('Bad child'); + } + + $child->setParent($this); + $name = mb_substr($child->name, mb_strlen($this->name) + 1); + + $this->children[$name] = $child; + } + + /** + * @return Traversable + */ + public function getIterator(): Traversable + { + return new ArrayIterator($this->children); + } + + /** + * @return int + */ + public function count(): int + { + return count($this->children); + } + + /** + * @return array + */ + #[\ReturnTypeWillChange] + public function __debugInfo() + { + return [ + 'name' => $this->name, + 'type' => $this->type, + 'label' => $this->label, + 'params' => $this->params, + 'actions' => $this->children + ]; + } +} diff --git a/system/src/Grav/Framework/Acl/Permissions.php b/system/src/Grav/Framework/Acl/Permissions.php new file mode 100644 index 0000000..324454a --- /dev/null +++ b/system/src/Grav/Framework/Acl/Permissions.php @@ -0,0 +1,249 @@ + + * @implements IteratorAggregate + */ +class Permissions implements ArrayAccess, Countable, IteratorAggregate +{ + /** @var array */ + protected $instances = []; + /** @var array */ + protected $actions = []; + /** @var array */ + protected $nested = []; + /** @var array */ + protected $types = []; + + /** + * @return array + */ + public function getInstances(): array + { + $iterator = new RecursiveActionIterator($this->actions); + $recursive = new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::SELF_FIRST); + + return iterator_to_array($recursive); + } + + /** + * @param string $name + * @return bool + */ + public function hasAction(string $name): bool + { + return isset($this->instances[$name]); + } + + /** + * @param string $name + * @return Action|null + */ + public function getAction(string $name): ?Action + { + return $this->instances[$name] ?? null; + } + + /** + * @param Action $action + * @return void + */ + public function addAction(Action $action): void + { + $name = $action->name; + $parent = $this->getParent($name); + if ($parent) { + $parent->addChild($action); + } else { + $this->actions[$name] = $action; + } + + $this->instances[$name] = $action; + + // If Action has children, add those, too. + foreach ($action->getChildren() as $child) { + $this->instances[$child->name] = $child; + } + } + + /** + * @return array + */ + public function getActions(): array + { + return $this->actions; + } + + /** + * @param Action[] $actions + * @return void + */ + public function addActions(array $actions): void + { + foreach ($actions as $action) { + $this->addAction($action); + } + } + + /** + * @param string $name + * @return bool + */ + public function hasType(string $name): bool + { + return isset($this->types[$name]); + } + + /** + * @param string $name + * @return Action|null + */ + public function getType(string $name): ?Action + { + return $this->types[$name] ?? null; + } + + /** + * @param string $name + * @param array $type + * @return void + */ + public function addType(string $name, array $type): void + { + $this->types[$name] = $type; + } + + /** + * @return array + */ + public function getTypes(): array + { + return $this->types; + } + + /** + * @param array $types + * @return void + */ + public function addTypes(array $types): void + { + $types = array_replace($this->types, $types); + + $this->types = $types; + } + + /** + * @param array|null $access + * @return Access + */ + public function getAccess(array $access = null): Access + { + return new Access($access ?? []); + } + + /** + * @param int|string $offset + * @return bool + */ + public function offsetExists($offset): bool + { + return isset($this->nested[$offset]); + } + + /** + * @param int|string $offset + * @return Action|null + */ + public function offsetGet($offset): ?Action + { + return $this->nested[$offset] ?? null; + } + + /** + * @param int|string $offset + * @param mixed $value + * @return void + */ + public function offsetSet($offset, $value): void + { + throw new RuntimeException(__METHOD__ . '(): Not Supported'); + } + + /** + * @param int|string $offset + * @return void + */ + public function offsetUnset($offset): void + { + throw new RuntimeException(__METHOD__ . '(): Not Supported'); + } + + /** + * @return int + */ + public function count(): int + { + return count($this->actions); + } + + /** + * @return ArrayIterator|Traversable + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return new ArrayIterator($this->actions); + } + + /** + * @return array + */ + #[\ReturnTypeWillChange] + public function __debugInfo() + { + return [ + 'actions' => $this->actions + ]; + } + + /** + * @param string $name + * @return Action|null + */ + protected function getParent(string $name): ?Action + { + if ($pos = strrpos($name, '.')) { + $parentName = substr($name, 0, $pos); + + $parent = $this->getAction($parentName); + if (!$parent) { + $parent = new Action($parentName); + $this->addAction($parent); + } + + return $parent; + } + + return null; + } +} diff --git a/system/src/Grav/Framework/Acl/PermissionsReader.php b/system/src/Grav/Framework/Acl/PermissionsReader.php new file mode 100644 index 0000000..5721452 --- /dev/null +++ b/system/src/Grav/Framework/Acl/PermissionsReader.php @@ -0,0 +1,186 @@ +content(); + $actions = $content['actions'] ?? []; + $types = $content['types'] ?? []; + + return static::fromArray($actions, $types); + } + + /** + * @param array $actions + * @param array $types + * @return Action[] + */ + public static function fromArray(array $actions, array $types): array + { + static::initTypes($types); + + $list = []; + foreach (static::read($actions) as $type => $data) { + $list[$type] = new Action($type, $data); + } + + return $list; + } + + /** + * @param array $actions + * @param string $prefix + * @return array + */ + public static function read(array $actions, string $prefix = ''): array + { + $list = []; + foreach ($actions as $name => $action) { + $prefixName = $prefix . $name; + $list[$prefixName] = null; + + // Support nested sets of actions. + if (isset($action['actions']) && is_array($action['actions'])) { + $innerList = static::read($action['actions'], "{$prefixName}."); + + $list += $innerList; + } + + unset($action['actions']); + + // Add defaults if they exist. + $action = static::addDefaults($action); + + // Build flat list of actions. + $list[$prefixName] = $action; + } + + return $list; + } + + /** + * @param array $types + * @return void + */ + protected static function initTypes(array $types) + { + static::$types = []; + + $dependencies = []; + foreach ($types as $type => $defaults) { + $current = array_fill_keys((array)($defaults['use'] ?? null), null); + $defType = $defaults['type'] ?? $type; + if ($type !== $defType) { + $current[$defaults['type']] = null; + } + + $dependencies[$type] = (object)$current; + } + + // Build dependency tree. + foreach ($dependencies as $type => $dep) { + foreach (get_object_vars($dep) as $k => &$val) { + if (null === $val) { + $val = $dependencies[$k] ?? new stdClass(); + } + } + unset($val); + } + + $encoded = json_encode($dependencies); + if ($encoded === false) { + throw new RuntimeException('json_encode(): failed to encode dependencies'); + } + $dependencies = json_decode($encoded, true); + + foreach (static::getDependencies($dependencies) as $type) { + $defaults = $types[$type] ?? null; + if ($defaults) { + static::$types[$type] = static::addDefaults($defaults); + } + } + } + + /** + * @param array $dependencies + * @return array + */ + protected static function getDependencies(array $dependencies): array + { + $list = [[]]; + foreach ($dependencies as $name => $deps) { + $current = $deps ? static::getDependencies($deps) : []; + $current[] = $name; + + $list[] = $current; + } + + return array_unique(array_merge(...$list)); + } + + /** + * @param array $action + * @return array + */ + protected static function addDefaults(array $action): array + { + $scopes = []; + + // Add used properties. + $use = (array)($action['use'] ?? null); + foreach ($use as $type) { + if (isset(static::$types[$type])) { + $used = static::$types[$type]; + unset($used['type']); + $scopes[] = $used; + } + } + unset($action['use']); + + // Add type defaults. + $type = $action['type'] ?? 'default'; + $defaults = static::$types[$type] ?? null; + if (is_array($defaults)) { + $scopes[] = $defaults; + } + + if ($scopes) { + $scopes[] = $action; + + $action = array_replace_recursive(...$scopes); + + $newType = $defaults['type'] ?? null; + if ($newType && $newType !== $type) { + $action['type'] = $newType; + } + } + + return $action; + } +} diff --git a/system/src/Grav/Framework/Acl/RecursiveActionIterator.php b/system/src/Grav/Framework/Acl/RecursiveActionIterator.php new file mode 100644 index 0000000..f4d4163 --- /dev/null +++ b/system/src/Grav/Framework/Acl/RecursiveActionIterator.php @@ -0,0 +1,64 @@ + + */ +class RecursiveActionIterator implements RecursiveIterator, \Countable +{ + use Constructor, Iterator, Countable; + + public $items; + + /** + * @see \Iterator::key() + * @return string + */ + #[\ReturnTypeWillChange] + public function key() + { + /** @var Action $current */ + $current = $this->current(); + + return $current->name; + } + + /** + * @see \RecursiveIterator::hasChildren() + * @return bool + */ + public function hasChildren(): bool + { + /** @var Action $current */ + $current = $this->current(); + + return $current->hasChildren(); + } + + /** + * @see \RecursiveIterator::getChildren() + * @return RecursiveActionIterator + */ + public function getChildren(): self + { + /** @var Action $current */ + $current = $this->current(); + + return new static($current->getChildren()); + } +} diff --git a/system/src/Grav/Framework/Cache/AbstractCache.php b/system/src/Grav/Framework/Cache/AbstractCache.php new file mode 100644 index 0000000..64394b3 --- /dev/null +++ b/system/src/Grav/Framework/Cache/AbstractCache.php @@ -0,0 +1,32 @@ +init($namespace, $defaultLifetime); + } +} diff --git a/system/src/Grav/Framework/Cache/Adapter/ChainCache.php b/system/src/Grav/Framework/Cache/Adapter/ChainCache.php new file mode 100644 index 0000000..086b75a --- /dev/null +++ b/system/src/Grav/Framework/Cache/Adapter/ChainCache.php @@ -0,0 +1,210 @@ +getMessage(), $e->getCode(), $e); + } + + if (!$caches) { + throw new InvalidArgumentException('At least one cache must be specified'); + } + + foreach ($caches as $cache) { + if (!$cache instanceof CacheInterface) { + throw new InvalidArgumentException( + sprintf( + "The class '%s' does not implement the '%s' interface", + get_class($cache), + CacheInterface::class + ) + ); + } + } + + $this->caches = array_values($caches); + $this->count = count($caches); + } + + /** + * @inheritdoc + */ + public function doGet($key, $miss) + { + foreach ($this->caches as $i => $cache) { + $value = $cache->doGet($key, $miss); + if ($value !== $miss) { + while (--$i >= 0) { + // Update all the previous caches with missing value. + $this->caches[$i]->doSet($key, $value, $this->getDefaultLifetime()); + } + + return $value; + } + } + + return $miss; + } + + /** + * @inheritdoc + */ + public function doSet($key, $value, $ttl) + { + $success = true; + $i = $this->count; + + while ($i--) { + $success = $this->caches[$i]->doSet($key, $value, $ttl) && $success; + } + + return $success; + } + + /** + * @inheritdoc + */ + public function doDelete($key) + { + $success = true; + $i = $this->count; + + while ($i--) { + $success = $this->caches[$i]->doDelete($key) && $success; + } + + return $success; + } + + /** + * @inheritdoc + */ + public function doClear() + { + $success = true; + $i = $this->count; + + while ($i--) { + $success = $this->caches[$i]->doClear() && $success; + } + + return $success; + } + + /** + * @inheritdoc + */ + public function doGetMultiple($keys, $miss) + { + $list = []; + /** + * @var int $i + * @var CacheInterface $cache + */ + foreach ($this->caches as $i => $cache) { + $list[$i] = $cache->doGetMultiple($keys, $miss); + + $keys = array_diff_key($keys, $list[$i]); + + if (!$keys) { + break; + } + } + + // Update all the previous caches with missing values. + $values = []; + /** + * @var int $i + * @var CacheInterface $items + */ + foreach (array_reverse($list) as $i => $items) { + $values += $items; + if ($i && $values) { + $this->caches[$i-1]->doSetMultiple($values, $this->getDefaultLifetime()); + } + } + + return $values; + } + + /** + * @inheritdoc + */ + public function doSetMultiple($values, $ttl) + { + $success = true; + $i = $this->count; + + while ($i--) { + $success = $this->caches[$i]->doSetMultiple($values, $ttl) && $success; + } + + return $success; + } + + /** + * @inheritdoc + */ + public function doDeleteMultiple($keys) + { + $success = true; + $i = $this->count; + + while ($i--) { + $success = $this->caches[$i]->doDeleteMultiple($keys) && $success; + } + + return $success; + } + + /** + * @inheritdoc + */ + public function doHas($key) + { + foreach ($this->caches as $cache) { + if ($cache->doHas($key)) { + return true; + } + } + + return false; + } +} diff --git a/system/src/Grav/Framework/Cache/Adapter/DoctrineCache.php b/system/src/Grav/Framework/Cache/Adapter/DoctrineCache.php new file mode 100644 index 0000000..451a422 --- /dev/null +++ b/system/src/Grav/Framework/Cache/Adapter/DoctrineCache.php @@ -0,0 +1,118 @@ +getMessage(), $e->getCode(), $e); + } + + // Set namespace to Doctrine Cache provider if it was given. + $namespace = $this->getNamespace(); + if ($namespace) { + $doctrineCache->setNamespace($namespace); + } + + $this->driver = $doctrineCache; + } + + /** + * @inheritdoc + */ + public function doGet($key, $miss) + { + $value = $this->driver->fetch($key); + + // Doctrine cache does not differentiate between no result and cached 'false'. Make sure that we do. + return $value !== false || $this->driver->contains($key) ? $value : $miss; + } + + /** + * @inheritdoc + */ + public function doSet($key, $value, $ttl) + { + return $this->driver->save($key, $value, (int) $ttl); + } + + /** + * @inheritdoc + */ + public function doDelete($key) + { + return $this->driver->delete($key); + } + + /** + * @inheritdoc + */ + public function doClear() + { + return $this->driver->deleteAll(); + } + + /** + * @inheritdoc + */ + public function doGetMultiple($keys, $miss) + { + return $this->driver->fetchMultiple($keys); + } + + /** + * @inheritdoc + */ + public function doSetMultiple($values, $ttl) + { + return $this->driver->saveMultiple($values, (int) $ttl); + } + + /** + * @inheritdoc + */ + public function doDeleteMultiple($keys) + { + return $this->driver->deleteMultiple($keys); + } + + /** + * @inheritdoc + */ + public function doHas($key) + { + return $this->driver->contains($key); + } +} diff --git a/system/src/Grav/Framework/Cache/Adapter/FileCache.php b/system/src/Grav/Framework/Cache/Adapter/FileCache.php new file mode 100644 index 0000000..ee75fc6 --- /dev/null +++ b/system/src/Grav/Framework/Cache/Adapter/FileCache.php @@ -0,0 +1,266 @@ +initFileCache($namespace, $folder ?? ''); + } catch (\Psr\SimpleCache\InvalidArgumentException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * @inheritdoc + */ + public function doGet($key, $miss) + { + $now = time(); + $file = $this->getFile($key); + + if (!file_exists($file) || !$h = @fopen($file, 'rb')) { + return $miss; + } + + if ($now >= (int) $expiresAt = fgets($h)) { + fclose($h); + @unlink($file); + } else { + $i = rawurldecode(rtrim((string)fgets($h))); + $value = stream_get_contents($h) ?: ''; + fclose($h); + + if ($i === $key) { + return unserialize($value, ['allowed_classes' => true]); + } + } + + return $miss; + } + + /** + * @inheritdoc + * @throws CacheException + */ + public function doSet($key, $value, $ttl) + { + $expiresAt = time() + (int)$ttl; + + $result = $this->write( + $this->getFile($key, true), + $expiresAt . "\n" . rawurlencode($key) . "\n" . serialize($value), + $expiresAt + ); + + if (!$result && !is_writable($this->directory)) { + throw new CacheException(sprintf('Cache directory is not writable (%s)', $this->directory)); + } + + return $result; + } + + /** + * @inheritdoc + */ + public function doDelete($key) + { + $file = $this->getFile($key); + + $result = false; + if (file_exists($file)) { + $result = @unlink($file); + $result &= !file_exists($file); + } + + return $result; + } + + /** + * @inheritdoc + */ + public function doClear() + { + $result = true; + $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->directory, FilesystemIterator::SKIP_DOTS)); + + foreach ($iterator as $file) { + $result = ($file->isDir() || @unlink($file) || !file_exists($file)) && $result; + } + + return $result; + } + + /** + * @inheritdoc + */ + public function doHas($key) + { + $file = $this->getFile($key); + + return file_exists($file) && (@filemtime($file) > time() || $this->doGet($key, null)); + } + + /** + * @param string $key + * @param bool $mkdir + * @return string + */ + protected function getFile($key, $mkdir = false) + { + $hash = str_replace('/', '-', base64_encode(hash('sha256', static::class . $key, true))); + $dir = $this->directory . $hash[0] . DIRECTORY_SEPARATOR . $hash[1] . DIRECTORY_SEPARATOR; + + if ($mkdir) { + $this->mkdir($dir); + } + + return $dir . substr($hash, 2, 20); + } + + /** + * @param string $namespace + * @param string $directory + * @return void + * @throws InvalidArgumentException + */ + protected function initFileCache($namespace, $directory) + { + if ($directory === '') { + $directory = sys_get_temp_dir() . '/grav-cache'; + } else { + $directory = realpath($directory) ?: $directory; + } + + if (isset($namespace[0])) { + if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) { + throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0])); + } + $directory .= DIRECTORY_SEPARATOR . $namespace; + } + + $this->mkdir($directory); + + $directory .= DIRECTORY_SEPARATOR; + // On Windows the whole path is limited to 258 chars + if ('\\' === DIRECTORY_SEPARATOR && strlen($directory) > 234) { + throw new InvalidArgumentException(sprintf('Cache folder is too long (%s)', $directory)); + } + $this->directory = $directory; + } + + /** + * @param string $file + * @param string $data + * @param int|null $expiresAt + * @return bool + */ + private function write($file, $data, $expiresAt = null) + { + set_error_handler(__CLASS__.'::throwError'); + + try { + if ($this->tmp === null) { + $this->tmp = $this->directory . uniqid('', true); + } + + file_put_contents($this->tmp, $data); + + if ($expiresAt !== null) { + touch($this->tmp, $expiresAt); + } + + return rename($this->tmp, $file); + } finally { + restore_error_handler(); + } + } + + /** + * @param string $dir + * @return void + * @throws RuntimeException + */ + private function mkdir($dir) + { + // Silence error for open_basedir; should fail in mkdir instead. + if (@is_dir($dir)) { + return; + } + + $success = @mkdir($dir, 0777, true); + + if (!$success) { + // Take yet another look, make sure that the folder doesn't exist. + clearstatcache(true, $dir); + if (!@is_dir($dir)) { + throw new RuntimeException(sprintf('Unable to create directory: %s', $dir)); + } + } + } + + /** + * @param int $type + * @param string $message + * @param string $file + * @param int $line + * @return bool + * @internal + * @throws ErrorException + */ + public static function throwError($type, $message, $file, $line) + { + throw new ErrorException($message, 0, $type, $file, $line); + } + + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function __destruct() + { + if ($this->tmp !== null && file_exists($this->tmp)) { + unlink($this->tmp); + } + } +} diff --git a/system/src/Grav/Framework/Cache/Adapter/MemoryCache.php b/system/src/Grav/Framework/Cache/Adapter/MemoryCache.php new file mode 100644 index 0000000..35ca29e --- /dev/null +++ b/system/src/Grav/Framework/Cache/Adapter/MemoryCache.php @@ -0,0 +1,83 @@ +cache)) { + return $miss; + } + + return $this->cache[$key]; + } + + /** + * @param string $key + * @param mixed $value + * @param int $ttl + * @return bool + */ + public function doSet($key, $value, $ttl) + { + $this->cache[$key] = $value; + + return true; + } + + /** + * @param string $key + * @return bool + */ + public function doDelete($key) + { + unset($this->cache[$key]); + + return true; + } + + /** + * @return bool + */ + public function doClear() + { + $this->cache = []; + + return true; + } + + /** + * @param string $key + * @return bool + */ + public function doHas($key) + { + return array_key_exists($key, $this->cache); + } +} diff --git a/system/src/Grav/Framework/Cache/Adapter/SessionCache.php b/system/src/Grav/Framework/Cache/Adapter/SessionCache.php new file mode 100644 index 0000000..c4dbc69 --- /dev/null +++ b/system/src/Grav/Framework/Cache/Adapter/SessionCache.php @@ -0,0 +1,107 @@ +doGetStored($key); + + return $stored ? $stored[self::VALUE] : $miss; + } + + /** + * @param string $key + * @param mixed $value + * @param int $ttl + * @return bool + */ + public function doSet($key, $value, $ttl) + { + $stored = [self::VALUE => $value]; + if (null !== $ttl) { + $stored[self::LIFETIME] = time() + $ttl; + } + + $_SESSION[$this->getNamespace()][$key] = $stored; + + return true; + } + + /** + * @param string $key + * @return bool + */ + public function doDelete($key) + { + unset($_SESSION[$this->getNamespace()][$key]); + + return true; + } + + /** + * @return bool + */ + public function doClear() + { + unset($_SESSION[$this->getNamespace()]); + + return true; + } + + /** + * @param string $key + * @return bool + */ + public function doHas($key) + { + return $this->doGetStored($key) !== null; + } + + /** + * @return string + */ + public function getNamespace() + { + return 'cache-' . parent::getNamespace(); + } + + /** + * @param string $key + * @return mixed|null + */ + protected function doGetStored($key) + { + $stored = $_SESSION[$this->getNamespace()][$key] ?? null; + + if (isset($stored[self::LIFETIME]) && $stored[self::LIFETIME] < time()) { + unset($_SESSION[$this->getNamespace()][$key]); + $stored = null; + } + + return $stored ?: null; + } +} diff --git a/system/src/Grav/Framework/Cache/CacheInterface.php b/system/src/Grav/Framework/Cache/CacheInterface.php new file mode 100644 index 0000000..ab162e5 --- /dev/null +++ b/system/src/Grav/Framework/Cache/CacheInterface.php @@ -0,0 +1,71 @@ + $values + * @param int|null $ttl + * @return mixed + */ + public function doSetMultiple($values, $ttl); + + /** + * @param string[] $keys + * @return mixed + */ + public function doDeleteMultiple($keys); + + /** + * @param string $key + * @return mixed + */ + public function doHas($key); +} diff --git a/system/src/Grav/Framework/Cache/CacheTrait.php b/system/src/Grav/Framework/Cache/CacheTrait.php new file mode 100644 index 0000000..3e28c8b --- /dev/null +++ b/system/src/Grav/Framework/Cache/CacheTrait.php @@ -0,0 +1,373 @@ +namespace = (string) $namespace; + $this->defaultLifetime = $this->convertTtl($defaultLifetime); + $this->miss = new stdClass; + } + + /** + * @param bool $validation + * @return void + */ + public function setValidation($validation) + { + $this->validation = (bool) $validation; + } + + /** + * @return string + */ + protected function getNamespace() + { + return $this->namespace; + } + + /** + * @return int|null + */ + protected function getDefaultLifetime() + { + return $this->defaultLifetime; + } + + /** + * @param string $key + * @param mixed|null $default + * @return mixed|null + * @throws InvalidArgumentException + */ + public function get($key, $default = null) + { + $this->validateKey($key); + + $value = $this->doGet($key, $this->miss); + + return $value !== $this->miss ? $value : $default; + } + + /** + * @param string $key + * @param mixed $value + * @param null|int|DateInterval $ttl + * @return bool + * @throws InvalidArgumentException + */ + public function set($key, $value, $ttl = null) + { + $this->validateKey($key); + + $ttl = $this->convertTtl($ttl); + + // If a negative or zero TTL is provided, the item MUST be deleted from the cache. + return null !== $ttl && $ttl <= 0 ? $this->doDelete($key) : $this->doSet($key, $value, $ttl); + } + + /** + * @param string $key + * @return bool + * @throws InvalidArgumentException + */ + public function delete($key) + { + $this->validateKey($key); + + return $this->doDelete($key); + } + + /** + * @return bool + */ + public function clear() + { + return $this->doClear(); + } + + /** + * @param iterable $keys + * @param mixed|null $default + * @return iterable + * @throws InvalidArgumentException + */ + public function getMultiple($keys, $default = null) + { + if ($keys instanceof Traversable) { + $keys = iterator_to_array($keys, false); + } elseif (!is_array($keys)) { + $isObject = is_object($keys); + throw new InvalidArgumentException( + sprintf( + 'Cache keys must be array or Traversable, "%s" given', + $isObject ? get_class($keys) : gettype($keys) + ) + ); + } + + if (empty($keys)) { + return []; + } + + $this->validateKeys($keys); + $keys = array_unique($keys); + $keys = array_combine($keys, $keys); + + $list = $this->doGetMultiple($keys, $this->miss); + + // Make sure that values are returned in the same order as the keys were given. + $values = []; + foreach ($keys as $key) { + if (!array_key_exists($key, $list) || $list[$key] === $this->miss) { + $values[$key] = $default; + } else { + $values[$key] = $list[$key]; + } + } + + return $values; + } + + /** + * @param iterable $values + * @param null|int|DateInterval $ttl + * @return bool + * @throws InvalidArgumentException + */ + public function setMultiple($values, $ttl = null) + { + if ($values instanceof Traversable) { + $values = iterator_to_array($values, true); + } elseif (!is_array($values)) { + $isObject = is_object($values); + throw new InvalidArgumentException( + sprintf( + 'Cache values must be array or Traversable, "%s" given', + $isObject ? get_class($values) : gettype($values) + ) + ); + } + + $keys = array_keys($values); + + if (empty($keys)) { + return true; + } + + $this->validateKeys($keys); + + $ttl = $this->convertTtl($ttl); + + // If a negative or zero TTL is provided, the item MUST be deleted from the cache. + return null !== $ttl && $ttl <= 0 ? $this->doDeleteMultiple($keys) : $this->doSetMultiple($values, $ttl); + } + + /** + * @param iterable $keys + * @return bool + * @throws InvalidArgumentException + */ + public function deleteMultiple($keys) + { + if ($keys instanceof Traversable) { + $keys = iterator_to_array($keys, false); + } elseif (!is_array($keys)) { + $isObject = is_object($keys); + throw new InvalidArgumentException( + sprintf( + 'Cache keys must be array or Traversable, "%s" given', + $isObject ? get_class($keys) : gettype($keys) + ) + ); + } + + if (empty($keys)) { + return true; + } + + $this->validateKeys($keys); + + return $this->doDeleteMultiple($keys); + } + + /** + * @param string $key + * @return bool + * @throws InvalidArgumentException + */ + public function has($key) + { + $this->validateKey($key); + + return $this->doHas($key); + } + + /** + * @param array $keys + * @param mixed $miss + * @return array + */ + public function doGetMultiple($keys, $miss) + { + $results = []; + + foreach ($keys as $key) { + $value = $this->doGet($key, $miss); + if ($value !== $miss) { + $results[$key] = $value; + } + } + + return $results; + } + + /** + * @param array $values + * @param int|null $ttl + * @return bool + */ + public function doSetMultiple($values, $ttl) + { + $success = true; + + foreach ($values as $key => $value) { + $success = $this->doSet($key, $value, $ttl) && $success; + } + + return $success; + } + + /** + * @param array $keys + * @return bool + */ + public function doDeleteMultiple($keys) + { + $success = true; + + foreach ($keys as $key) { + $success = $this->doDelete($key) && $success; + } + + return $success; + } + + /** + * @param string|mixed $key + * @return void + * @throws InvalidArgumentException + */ + protected function validateKey($key) + { + if (!is_string($key)) { + throw new InvalidArgumentException( + sprintf( + 'Cache key must be string, "%s" given', + is_object($key) ? get_class($key) : gettype($key) + ) + ); + } + if (!isset($key[0])) { + throw new InvalidArgumentException('Cache key length must be greater than zero'); + } + if (strlen($key) > 64) { + throw new InvalidArgumentException( + sprintf('Cache key length must be less than 65 characters, key had %d characters', strlen($key)) + ); + } + if (strpbrk($key, '{}()/\@:') !== false) { + throw new InvalidArgumentException( + sprintf('Cache key "%s" contains reserved characters {}()/\@:', $key) + ); + } + } + + /** + * @param array $keys + * @return void + * @throws InvalidArgumentException + */ + protected function validateKeys($keys) + { + if (!$this->validation) { + return; + } + + foreach ($keys as $key) { + $this->validateKey($key); + } + } + + /** + * @param null|int|DateInterval $ttl + * @return int|null + * @throws InvalidArgumentException + */ + protected function convertTtl($ttl) + { + if ($ttl === null) { + return $this->getDefaultLifetime(); + } + + if (is_int($ttl)) { + return $ttl; + } + + if ($ttl instanceof DateInterval) { + $date = DateTime::createFromFormat('U', '0'); + $ttl = $date ? (int)$date->add($ttl)->format('U') : 0; + } + + throw new InvalidArgumentException( + sprintf( + 'Expiration date must be an integer, a DateInterval or null, "%s" given', + is_object($ttl) ? get_class($ttl) : gettype($ttl) + ) + ); + } +} diff --git a/system/src/Grav/Framework/Cache/Exception/CacheException.php b/system/src/Grav/Framework/Cache/Exception/CacheException.php new file mode 100644 index 0000000..17ec618 --- /dev/null +++ b/system/src/Grav/Framework/Cache/Exception/CacheException.php @@ -0,0 +1,21 @@ + + * @implements FileCollectionInterface + */ +class AbstractFileCollection extends AbstractLazyCollection implements FileCollectionInterface +{ + /** @var string */ + protected $path; + /** @var RecursiveDirectoryIterator|RecursiveUniformResourceIterator */ + protected $iterator; + /** @var callable */ + protected $createObjectFunction; + /** @var callable|null */ + protected $filterFunction; + /** @var int */ + protected $flags; + /** @var int */ + protected $nestingLimit; + + /** + * @param string $path + */ + protected function __construct($path) + { + $this->path = $path; + $this->flags = self::INCLUDE_FILES | self::INCLUDE_FOLDERS; + $this->nestingLimit = 0; + $this->createObjectFunction = [$this, 'createObject']; + + $this->setIterator(); + } + + /** + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * @param Criteria $criteria + * @return ArrayCollection + * @phpstan-return ArrayCollection + * @todo Implement lazy matching + */ + public function matching(Criteria $criteria) + { + $expr = $criteria->getWhereExpression(); + + $oldFilter = $this->filterFunction; + if ($expr) { + $visitor = new ClosureExpressionVisitor(); + $filter = $visitor->dispatch($expr); + $this->addFilter($filter); + } + + $filtered = $this->doInitializeByIterator($this->iterator, $this->nestingLimit); + $this->filterFunction = $oldFilter; + + if ($orderings = $criteria->getOrderings()) { + $next = null; + /** + * @var string $field + * @var string $ordering + */ + foreach (array_reverse($orderings) as $field => $ordering) { + $next = ClosureExpressionVisitor::sortByField($field, $ordering === Criteria::DESC ? -1 : 1, $next); + } + /** @phpstan-ignore-next-line */ + if (null === $next) { + throw new RuntimeException('Criteria is missing orderings'); + } + + uasort($filtered, $next); + } else { + ksort($filtered); + } + + $offset = $criteria->getFirstResult(); + $length = $criteria->getMaxResults(); + + if ($offset || $length) { + $filtered = array_slice($filtered, (int)$offset, $length); + } + + return new ArrayCollection($filtered); + } + + /** + * @return void + */ + protected function setIterator() + { + $iteratorFlags = RecursiveDirectoryIterator::SKIP_DOTS + FilesystemIterator::UNIX_PATHS + + FilesystemIterator::CURRENT_AS_SELF + FilesystemIterator::FOLLOW_SYMLINKS; + + if (strpos($this->path, '://')) { + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + $this->iterator = $locator->getRecursiveIterator($this->path, $iteratorFlags); + } else { + $this->iterator = new RecursiveDirectoryIterator($this->path, $iteratorFlags); + } + } + + /** + * @param callable $filterFunction + * @return $this + */ + protected function addFilter(callable $filterFunction) + { + if ($this->filterFunction) { + $oldFilterFunction = $this->filterFunction; + $this->filterFunction = function ($expr) use ($oldFilterFunction, $filterFunction) { + return $oldFilterFunction($expr) && $filterFunction($expr); + }; + } else { + $this->filterFunction = $filterFunction; + } + + return $this; + } + + /** + * {@inheritDoc} + */ + protected function doInitialize() + { + $filtered = $this->doInitializeByIterator($this->iterator, $this->nestingLimit); + ksort($filtered); + + $this->collection = new ArrayCollection($filtered); + } + + /** + * @param SeekableIterator $iterator + * @param int $nestingLimit + * @return array + * @phpstan-param SeekableIterator $iterator + */ + protected function doInitializeByIterator(SeekableIterator $iterator, $nestingLimit) + { + $children = []; + $objects = []; + $filter = $this->filterFunction; + $objectFunction = $this->createObjectFunction; + + /** @var RecursiveDirectoryIterator $file */ + foreach ($iterator as $file) { + // Skip files if they shouldn't be included. + if (!($this->flags & static::INCLUDE_FILES) && $file->isFile()) { + continue; + } + + // Apply main filter. + if ($filter && !$filter($file)) { + continue; + } + + // Include children if the recursive flag is set. + if (($this->flags & static::RECURSIVE) && $nestingLimit > 0 && $file->hasChildren()) { + $children[] = $file->getChildren(); + } + + // Skip folders if they shouldn't be included. + if (!($this->flags & static::INCLUDE_FOLDERS) && $file->isDir()) { + continue; + } + + $object = $objectFunction($file); + $objects[$object->key] = $object; + } + + if ($children) { + $objects += $this->doInitializeChildren($children, $nestingLimit - 1); + } + + return $objects; + } + + /** + * @param array $children + * @param int $nestingLimit + * @return array + */ + protected function doInitializeChildren(array $children, $nestingLimit) + { + $objects = []; + foreach ($children as $iterator) { + $objects += $this->doInitializeByIterator($iterator, $nestingLimit); + } + + return $objects; + } + + /** + * @param RecursiveDirectoryIterator $file + * @return object + */ + protected function createObject($file) + { + return (object) [ + 'key' => $file->getSubPathname(), + 'type' => $file->isDir() ? 'folder' : 'file:' . $file->getExtension(), + 'url' => method_exists($file, 'getUrl') ? $file->getUrl() : null, + 'pathname' => $file->getPathname(), + 'mtime' => $file->getMTime() + ]; + } +} diff --git a/system/src/Grav/Framework/Collection/AbstractIndexCollection.php b/system/src/Grav/Framework/Collection/AbstractIndexCollection.php new file mode 100644 index 0000000..710c415 --- /dev/null +++ b/system/src/Grav/Framework/Collection/AbstractIndexCollection.php @@ -0,0 +1,574 @@ + + */ +abstract class AbstractIndexCollection implements CollectionInterface +{ + use Serializable; + + /** + * @var array + * @phpstan-var array + */ + private $entries; + + /** + * Initializes a new IndexCollection. + * + * @param array $entries + * @phpstan-param array $entries + */ + public function __construct(array $entries = []) + { + $this->entries = $entries; + } + + /** + * {@inheritDoc} + */ + public function toArray() + { + return $this->loadElements($this->entries); + } + + /** + * {@inheritDoc} + */ + public function first() + { + $value = reset($this->entries); + $key = (string)key($this->entries); + + return $this->loadElement($key, $value); + } + + /** + * {@inheritDoc} + */ + public function last() + { + $value = end($this->entries); + $key = (string)key($this->entries); + + return $this->loadElement($key, $value); + } + + /** + * {@inheritDoc} + */ + #[\ReturnTypeWillChange] + public function key() + { + /** @phpstan-var TKey */ + return (string)key($this->entries); + } + + /** + * {@inheritDoc} + */ + #[\ReturnTypeWillChange] + public function next() + { + $value = next($this->entries); + $key = (string)key($this->entries); + + return $this->loadElement($key, $value); + } + + /** + * {@inheritDoc} + */ + #[\ReturnTypeWillChange] + public function current() + { + $value = current($this->entries); + $key = (string)key($this->entries); + + return $this->loadElement($key, $value); + } + + /** + * {@inheritDoc} + */ + public function remove($key) + { + if (!array_key_exists($key, $this->entries)) { + return null; + } + + $value = $this->entries[$key]; + unset($this->entries[$key]); + + return $this->loadElement((string)$key, $value); + } + + /** + * {@inheritDoc} + */ + public function removeElement($element) + { + $key = $this->isAllowedElement($element) ? $this->getCurrentKey($element) : null; + + if (null !== $key || !isset($this->entries[$key])) { + return false; + } + + unset($this->entries[$key]); + + return true; + } + + /** + * Required by interface ArrayAccess. + * + * @param string|int|null $offset + * @return bool + * @phpstan-param TKey|null $offset + */ + #[\ReturnTypeWillChange] + public function offsetExists($offset) + { + /** @phpstan-ignore-next-line phpstan bug? */ + return $offset !== null ? $this->containsKey($offset) : false; + } + + /** + * Required by interface ArrayAccess. + * + * @param string|int|null $offset + * @return mixed + * @phpstan-param TKey|null $offset + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + /** @phpstan-ignore-next-line phpstan bug? */ + return $offset !== null ? $this->get($offset) : null; + } + + /** + * Required by interface ArrayAccess. + * + * @param string|int|null $offset + * @param mixed $value + * @return void + * @phpstan-param TKey|null $offset + */ + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value) + { + if (null === $offset) { + $this->add($value); + } else { + /** @phpstan-ignore-next-line phpstan bug? */ + $this->set($offset, $value); + } + } + + /** + * Required by interface ArrayAccess. + * + * @param string|int|null $offset + * @return void + * @phpstan-param TKey|null $offset + */ + #[\ReturnTypeWillChange] + public function offsetUnset($offset) + { + if ($offset !== null) { + /** @phpstan-ignore-next-line phpstan bug? */ + $this->remove($offset); + } + } + + /** + * {@inheritDoc} + */ + public function containsKey($key) + { + return isset($this->entries[$key]) || array_key_exists($key, $this->entries); + } + + /** + * {@inheritDoc} + */ + public function contains($element) + { + $key = $this->isAllowedElement($element) ? $this->getCurrentKey($element) : null; + + return $key && isset($this->entries[$key]); + } + + /** + * {@inheritDoc} + */ + public function exists(Closure $p) + { + return $this->loadCollection($this->entries)->exists($p); + } + + /** + * {@inheritDoc} + */ + public function indexOf($element) + { + $key = $this->isAllowedElement($element) ? $this->getCurrentKey($element) : null; + + return $key && isset($this->entries[$key]) ? $key : false; + } + + /** + * {@inheritDoc} + */ + public function get($key) + { + if (!isset($this->entries[$key])) { + return null; + } + + return $this->loadElement((string)$key, $this->entries[$key]); + } + + /** + * {@inheritDoc} + */ + public function getKeys() + { + return array_keys($this->entries); + } + + /** + * {@inheritDoc} + */ + public function getValues() + { + return array_values($this->loadElements($this->entries)); + } + + /** + * {@inheritDoc} + */ + #[\ReturnTypeWillChange] + public function count() + { + return count($this->entries); + } + + /** + * {@inheritDoc} + */ + public function set($key, $value) + { + if (!$this->isAllowedElement($value)) { + throw new InvalidArgumentException('Invalid argument $value'); + } + + $this->entries[$key] = $this->getElementMeta($value); + } + + /** + * {@inheritDoc} + */ + public function add($element) + { + if (!$this->isAllowedElement($element)) { + throw new InvalidArgumentException('Invalid argument $element'); + } + + $this->entries[$this->getCurrentKey($element)] = $this->getElementMeta($element); + + return true; + } + + /** + * {@inheritDoc} + */ + public function isEmpty() + { + return empty($this->entries); + } + + /** + * Required by interface IteratorAggregate. + * + * {@inheritDoc} + * @phpstan-return Iterator + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return new ArrayIterator($this->loadElements()); + } + + /** + * {@inheritDoc} + */ + public function map(Closure $func) + { + return $this->loadCollection($this->entries)->map($func); + } + + /** + * {@inheritDoc} + */ + public function filter(Closure $p) + { + return $this->loadCollection($this->entries)->filter($p); + } + + /** + * {@inheritDoc} + */ + public function forAll(Closure $p) + { + return $this->loadCollection($this->entries)->forAll($p); + } + + /** + * {@inheritDoc} + */ + public function partition(Closure $p) + { + return $this->loadCollection($this->entries)->partition($p); + } + + /** + * Returns a string representation of this object. + * + * @return string + */ + #[\ReturnTypeWillChange] + public function __toString() + { + return __CLASS__ . '@' . spl_object_hash($this); + } + + /** + * {@inheritDoc} + */ + public function clear() + { + $this->entries = []; + } + + /** + * {@inheritDoc} + */ + public function slice($offset, $length = null) + { + return $this->loadElements(array_slice($this->entries, $offset, $length, true)); + } + + /** + * @param int $start + * @param int|null $limit + * @return static + * @phpstan-return static + */ + public function limit($start, $limit = null) + { + return $this->createFrom(array_slice($this->entries, $start, $limit, true)); + } + + /** + * Reverse the order of the items. + * + * @return static + * @phpstan-return static + */ + public function reverse() + { + return $this->createFrom(array_reverse($this->entries)); + } + + /** + * Shuffle items. + * + * @return static + * @phpstan-return static + */ + public function shuffle() + { + $keys = $this->getKeys(); + shuffle($keys); + + return $this->createFrom(array_replace(array_flip($keys), $this->entries)); + } + + /** + * Select items from collection. + * + * Collection is returned in the order of $keys given to the function. + * + * @param array $keys + * @return static + * @phpstan-return static + */ + public function select(array $keys) + { + $list = []; + foreach ($keys as $key) { + if (isset($this->entries[$key])) { + $list[$key] = $this->entries[$key]; + } + } + + return $this->createFrom($list); + } + + /** + * Un-select items from collection. + * + * @param array $keys + * @return static + * @phpstan-return static + */ + public function unselect(array $keys) + { + return $this->select(array_diff($this->getKeys(), $keys)); + } + + /** + * Split collection into chunks. + * + * @param int $size Size of each chunk. + * @return array + * @phpstan-return array> + */ + public function chunk($size) + { + /** @phpstan-var array> */ + return $this->loadCollection($this->entries)->chunk($size); + } + + /** + * @return array + */ + public function __serialize(): array + { + return [ + 'entries' => $this->entries + ]; + } + + /** + * @param array $data + * @return void + */ + public function __unserialize(array $data): void + { + $this->entries = $data['entries']; + } + + /** + * Implements JsonSerializable interface. + * + * @return array + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->loadCollection()->jsonSerialize(); + } + + /** + * Creates a new instance from the specified elements. + * + * This method is provided for derived classes to specify how a new + * instance should be created when constructor semantics have changed. + * + * @param array $entries Elements. + * @return static + * @phpstan-return static + */ + protected function createFrom(array $entries) + { + return new static($entries); + } + + /** + * @return array + */ + protected function getEntries(): array + { + return $this->entries; + } + + /** + * @param array $entries + * @return void + * @phpstan-param array $entries + */ + protected function setEntries(array $entries): void + { + $this->entries = $entries; + } + + /** + * @param FlexObjectInterface $element + * @return string + * @phpstan-param T $element + * @phpstan-return TKey + */ + protected function getCurrentKey($element) + { + return $element->getKey(); + } + + /** + * @param string $key + * @param mixed $value + * @return mixed|null + */ + abstract protected function loadElement($key, $value); + + /** + * @param array|null $entries + * @return array + * @phpstan-return array + */ + abstract protected function loadElements(array $entries = null): array; + + /** + * @param array|null $entries + * @return CollectionInterface + * @phpstan-return C + */ + abstract protected function loadCollection(array $entries = null): CollectionInterface; + + /** + * @param mixed $value + * @return bool + */ + abstract protected function isAllowedElement($value): bool; + + /** + * @param mixed $element + * @return mixed + */ + abstract protected function getElementMeta($element); +} diff --git a/system/src/Grav/Framework/Collection/AbstractLazyCollection.php b/system/src/Grav/Framework/Collection/AbstractLazyCollection.php new file mode 100644 index 0000000..045685e --- /dev/null +++ b/system/src/Grav/Framework/Collection/AbstractLazyCollection.php @@ -0,0 +1,97 @@ + + * @implements CollectionInterface + */ +abstract class AbstractLazyCollection extends BaseAbstractLazyCollection implements CollectionInterface +{ + /** + * @par ArrayCollection + * @phpstan-var ArrayCollection + */ + protected $collection; + + /** + * {@inheritDoc} + * @phpstan-return ArrayCollection + */ + public function reverse() + { + $this->initialize(); + + return $this->collection->reverse(); + } + + /** + * {@inheritDoc} + * @phpstan-return ArrayCollection + */ + public function shuffle() + { + $this->initialize(); + + return $this->collection->shuffle(); + } + + /** + * {@inheritDoc} + */ + public function chunk($size) + { + $this->initialize(); + + return $this->collection->chunk($size); + } + + /** + * {@inheritDoc} + * @phpstan-param array $keys + * @phpstan-return ArrayCollection + */ + public function select(array $keys) + { + $this->initialize(); + + return $this->collection->select($keys); + } + + /** + * {@inheritDoc} + * @phpstan-param array $keys + * @phpstan-return ArrayCollection + */ + public function unselect(array $keys) + { + $this->initialize(); + + return $this->collection->unselect($keys); + } + + /** + * @return array + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + $this->initialize(); + + return $this->collection->jsonSerialize(); + } +} diff --git a/system/src/Grav/Framework/Collection/ArrayCollection.php b/system/src/Grav/Framework/Collection/ArrayCollection.php new file mode 100644 index 0000000..a99291f --- /dev/null +++ b/system/src/Grav/Framework/Collection/ArrayCollection.php @@ -0,0 +1,117 @@ + + * @implements CollectionInterface + */ +class ArrayCollection extends BaseArrayCollection implements CollectionInterface +{ + /** + * Reverse the order of the items. + * + * @return static + * @phpstan-return static + */ + public function reverse() + { + $keys = array_reverse($this->toArray()); + + /** @phpstan-var static */ + return $this->createFrom($keys); + } + + /** + * Shuffle items. + * + * @return static + * @phpstan-return static + */ + public function shuffle() + { + $keys = $this->getKeys(); + shuffle($keys); + $keys = array_replace(array_flip($keys), $this->toArray()); + + /** @phpstan-var static */ + return $this->createFrom($keys); + } + + /** + * Split collection into chunks. + * + * @param int $size Size of each chunk. + * @return array + * @phpstan-return array> + */ + public function chunk($size) + { + /** @phpstan-var array> */ + return array_chunk($this->toArray(), $size, true); + } + + /** + * Select items from collection. + * + * Collection is returned in the order of $keys given to the function. + * + * @param array $keys + * @return static + * @phpstan-param TKey[] $keys + * @phpstan-return static + */ + public function select(array $keys) + { + $list = []; + foreach ($keys as $key) { + if ($this->containsKey($key)) { + $list[$key] = $this->get($key); + } + } + + /** @phpstan-var static */ + return $this->createFrom($list); + } + + /** + * Un-select items from collection. + * + * @param array $keys + * @return static + * @phpstan-param TKey[] $keys + * @phpstan-return static + */ + public function unselect(array $keys) + { + $list = array_diff($this->getKeys(), $keys); + + /** @phpstan-var static */ + return $this->select($list); + } + + /** + * Implements JsonSerializable interface. + * + * @return array + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } +} diff --git a/system/src/Grav/Framework/Collection/CollectionInterface.php b/system/src/Grav/Framework/Collection/CollectionInterface.php new file mode 100644 index 0000000..a629756 --- /dev/null +++ b/system/src/Grav/Framework/Collection/CollectionInterface.php @@ -0,0 +1,69 @@ + + */ +interface CollectionInterface extends Collection, JsonSerializable +{ + /** + * Reverse the order of the items. + * + * @return CollectionInterface + * @phpstan-return static + */ + public function reverse(); + + /** + * Shuffle items. + * + * @return CollectionInterface + * @phpstan-return static + */ + public function shuffle(); + + /** + * Split collection into chunks. + * + * @param int $size Size of each chunk. + * @return array + * @phpstan-return array> + */ + public function chunk($size); + + /** + * Select items from collection. + * + * Collection is returned in the order of $keys given to the function. + * + * @param array $keys + * @return CollectionInterface + * @phpstan-return static + */ + public function select(array $keys); + + /** + * Un-select items from collection. + * + * @param array $keys + * @return CollectionInterface + * @phpstan-return static + */ + public function unselect(array $keys); +} diff --git a/system/src/Grav/Framework/Collection/FileCollection.php b/system/src/Grav/Framework/Collection/FileCollection.php new file mode 100644 index 0000000..73580fa --- /dev/null +++ b/system/src/Grav/Framework/Collection/FileCollection.php @@ -0,0 +1,97 @@ + + */ +class FileCollection extends AbstractFileCollection +{ + /** + * @param string $path + * @param int $flags + */ + public function __construct($path, $flags = null) + { + parent::__construct($path); + + $this->flags = (int)($flags ?: self::INCLUDE_FILES | self::INCLUDE_FOLDERS | self::RECURSIVE); + + $this->setIterator(); + $this->setFilter(); + $this->setObjectBuilder(); + $this->setNestingLimit(); + } + + /** + * @return int + */ + public function getFlags() + { + return $this->flags; + } + + /** + * @return int + */ + public function getNestingLimit() + { + return $this->nestingLimit; + } + + /** + * @param int $limit + * @return $this + */ + public function setNestingLimit($limit = 99) + { + $this->nestingLimit = (int) $limit; + + return $this; + } + + /** + * @param callable|null $filterFunction + * @return $this + */ + public function setFilter(callable $filterFunction = null) + { + $this->filterFunction = $filterFunction; + + return $this; + } + + /** + * @param callable $filterFunction + * @return $this + */ + public function addFilter(callable $filterFunction) + { + parent::addFilter($filterFunction); + + return $this; + } + + /** + * @param callable|null $objectFunction + * @return $this + */ + public function setObjectBuilder(callable $objectFunction = null) + { + $this->createObjectFunction = $objectFunction ?: [$this, 'createObject']; + + return $this; + } +} diff --git a/system/src/Grav/Framework/Collection/FileCollectionInterface.php b/system/src/Grav/Framework/Collection/FileCollectionInterface.php new file mode 100644 index 0000000..8cbc2ea --- /dev/null +++ b/system/src/Grav/Framework/Collection/FileCollectionInterface.php @@ -0,0 +1,33 @@ + + * @extends Selectable + */ +interface FileCollectionInterface extends CollectionInterface, Selectable +{ + public const INCLUDE_FILES = 1; + public const INCLUDE_FOLDERS = 2; + public const RECURSIVE = 4; + + /** + * @return string + */ + public function getPath(); +} diff --git a/system/src/Grav/Framework/Compat/Serializable.php b/system/src/Grav/Framework/Compat/Serializable.php new file mode 100644 index 0000000..da1cce3 --- /dev/null +++ b/system/src/Grav/Framework/Compat/Serializable.php @@ -0,0 +1,47 @@ +__serialize()); + } + + /** + * @param string $serialized + * @return void + */ + final public function unserialize($serialized): void + { + $this->__unserialize(unserialize($serialized, ['allowed_classes' => $this->getUnserializeAllowedClasses()])); + } + + /** + * @return array|bool + */ + protected function getUnserializeAllowedClasses() + { + return false; + } +} diff --git a/system/src/Grav/Framework/ContentBlock/ContentBlock.php b/system/src/Grav/Framework/ContentBlock/ContentBlock.php new file mode 100644 index 0000000..f580006 --- /dev/null +++ b/system/src/Grav/Framework/ContentBlock/ContentBlock.php @@ -0,0 +1,303 @@ +setContent('my inner content'); + * $outerBlock = ContentBlock::create(); + * $outerBlock->setContent(sprintf('Inside my outer block I have %s.', $innerBlock->getToken())); + * $outerBlock->addBlock($innerBlock); + * echo $outerBlock; + * + * @package Grav\Framework\ContentBlock + */ +class ContentBlock implements ContentBlockInterface +{ + use Serializable; + + /** @var int */ + protected $version = 1; + /** @var string */ + protected $id; + /** @var string */ + protected $tokenTemplate = '@@BLOCK-%s@@'; + /** @var string */ + protected $content = ''; + /** @var array */ + protected $blocks = []; + /** @var string */ + protected $checksum; + /** @var bool */ + protected $cached = true; + + /** + * @param string|null $id + * @return static + */ + public static function create($id = null) + { + return new static($id); + } + + /** + * @param array $serialized + * @return ContentBlockInterface + * @throws InvalidArgumentException + */ + public static function fromArray(array $serialized) + { + try { + $type = $serialized['_type'] ?? null; + $id = $serialized['id'] ?? null; + + if (!$type || !$id || !is_a($type, ContentBlockInterface::class, true)) { + throw new InvalidArgumentException('Bad data'); + } + + /** @var ContentBlockInterface $instance */ + $instance = new $type($id); + $instance->build($serialized); + } catch (Exception $e) { + throw new InvalidArgumentException(sprintf('Cannot unserialize Block: %s', $e->getMessage()), $e->getCode(), $e); + } + + return $instance; + } + + /** + * Block constructor. + * + * @param string|null $id + */ + public function __construct($id = null) + { + $this->id = $id ? (string) $id : $this->generateId(); + } + + /** + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * @return string + */ + public function getToken() + { + return sprintf($this->tokenTemplate, $this->getId()); + } + + /** + * @return array + */ + public function toArray() + { + $blocks = []; + /** @var ContentBlockInterface $block */ + foreach ($this->blocks as $block) { + $blocks[$block->getId()] = $block->toArray(); + } + + $array = [ + '_type' => get_class($this), + '_version' => $this->version, + 'id' => $this->id, + 'cached' => $this->cached + ]; + + if ($this->checksum) { + $array['checksum'] = $this->checksum; + } + + if ($this->content) { + $array['content'] = $this->content; + } + + if ($blocks) { + $array['blocks'] = $blocks; + } + + return $array; + } + + /** + * @return string + */ + public function toString() + { + if (!$this->blocks) { + return (string) $this->content; + } + + $tokens = []; + $replacements = []; + foreach ($this->blocks as $block) { + $tokens[] = $block->getToken(); + $replacements[] = $block->toString(); + } + + return str_replace($tokens, $replacements, (string) $this->content); + } + + /** + * @return string + */ + #[\ReturnTypeWillChange] + public function __toString() + { + try { + return $this->toString(); + } catch (Exception $e) { + return sprintf('Error while rendering block: %s', $e->getMessage()); + } + } + + /** + * @param array $serialized + * @return void + * @throws RuntimeException + */ + public function build(array $serialized) + { + $this->checkVersion($serialized); + + $this->id = $serialized['id'] ?? $this->generateId(); + $this->checksum = $serialized['checksum'] ?? null; + $this->cached = $serialized['cached'] ?? null; + + if (isset($serialized['content'])) { + $this->setContent($serialized['content']); + } + + $blocks = isset($serialized['blocks']) ? (array) $serialized['blocks'] : []; + foreach ($blocks as $block) { + $this->addBlock(self::fromArray($block)); + } + } + + /** + * @return bool + */ + public function isCached() + { + if (!$this->cached) { + return false; + } + + foreach ($this->blocks as $block) { + if (!$block->isCached()) { + return false; + } + } + + return true; + } + + /** + * @return $this + */ + public function disableCache() + { + $this->cached = false; + + return $this; + } + + /** + * @param string $checksum + * @return $this + */ + public function setChecksum($checksum) + { + $this->checksum = $checksum; + + return $this; + } + + /** + * @return string + */ + public function getChecksum() + { + return $this->checksum; + } + + /** + * @param string $content + * @return $this + */ + public function setContent($content) + { + $this->content = $content; + + return $this; + } + + /** + * @param ContentBlockInterface $block + * @return $this + */ + public function addBlock(ContentBlockInterface $block) + { + $this->blocks[$block->getId()] = $block; + + return $this; + } + + /** + * @return array + */ + final public function __serialize(): array + { + return $this->toArray(); + } + + /** + * @param array $data + * @return void + */ + final public function __unserialize(array $data): void + { + $this->build($data); + } + + /** + * @return string + */ + protected function generateId() + { + return uniqid('', true); + } + + /** + * @param array $serialized + * @return void + * @throws RuntimeException + */ + protected function checkVersion(array $serialized) + { + $version = isset($serialized['_version']) ? (int) $serialized['_version'] : 1; + if ($version !== $this->version) { + throw new RuntimeException(sprintf('Unsupported version %s', $version)); + } + } +} diff --git a/system/src/Grav/Framework/ContentBlock/ContentBlockInterface.php b/system/src/Grav/Framework/ContentBlock/ContentBlockInterface.php new file mode 100644 index 0000000..abb264c --- /dev/null +++ b/system/src/Grav/Framework/ContentBlock/ContentBlockInterface.php @@ -0,0 +1,90 @@ +getAssetsFast(); + + $this->sortAssets($assets['styles']); + $this->sortAssets($assets['scripts']); + $this->sortAssets($assets['links']); + $this->sortAssets($assets['html']); + + return $assets; + } + + /** + * @return array + */ + public function getFrameworks() + { + $assets = $this->getAssetsFast(); + + return array_keys($assets['frameworks']); + } + + /** + * @param string $location + * @return array + */ + public function getStyles($location = 'head') + { + return $this->getAssetsInLocation('styles', $location); + } + + /** + * @param string $location + * @return array + */ + public function getScripts($location = 'head') + { + return $this->getAssetsInLocation('scripts', $location); + } + + /** + * @param string $location + * @return array + */ + public function getLinks($location = 'head') + { + return $this->getAssetsInLocation('links', $location); + } + + /** + * @param string $location + * @return array + */ + public function getHtml($location = 'bottom') + { + return $this->getAssetsInLocation('html', $location); + } + + /** + * @return array + */ + public function toArray() + { + $array = parent::toArray(); + + if ($this->frameworks) { + $array['frameworks'] = $this->frameworks; + } + if ($this->styles) { + $array['styles'] = $this->styles; + } + if ($this->scripts) { + $array['scripts'] = $this->scripts; + } + if ($this->links) { + $array['links'] = $this->links; + } + if ($this->html) { + $array['html'] = $this->html; + } + + return $array; + } + + /** + * @param array $serialized + * @return void + * @throws RuntimeException + */ + public function build(array $serialized) + { + parent::build($serialized); + + $this->frameworks = isset($serialized['frameworks']) ? (array) $serialized['frameworks'] : []; + $this->styles = isset($serialized['styles']) ? (array) $serialized['styles'] : []; + $this->scripts = isset($serialized['scripts']) ? (array) $serialized['scripts'] : []; + $this->links = isset($serialized['links']) ? (array) $serialized['links'] : []; + $this->html = isset($serialized['html']) ? (array) $serialized['html'] : []; + } + + /** + * @param string $framework + * @return $this + */ + public function addFramework($framework) + { + $this->frameworks[$framework] = 1; + + return $this; + } + + /** + * @param string|array $element + * @param int $priority + * @param string $location + * @return bool + * + * @example $block->addStyle('assets/js/my.js'); + * @example $block->addStyle(['href' => 'assets/js/my.js', 'media' => 'screen']); + */ + public function addStyle($element, $priority = 0, $location = 'head') + { + if (!is_array($element)) { + $element = ['href' => (string) $element]; + } + if (empty($element['href'])) { + return false; + } + if (!isset($this->styles[$location])) { + $this->styles[$location] = []; + } + + $id = !empty($element['id']) ? ['id' => (string) $element['id']] : []; + $href = $element['href']; + $type = !empty($element['type']) ? (string) $element['type'] : 'text/css'; + $media = !empty($element['media']) ? (string) $element['media'] : null; + unset( + $element['tag'], + $element['id'], + $element['rel'], + $element['content'], + $element['href'], + $element['type'], + $element['media'] + ); + + $this->styles[$location][md5($href) . sha1($href)] = [ + ':type' => 'file', + ':priority' => (int) $priority, + 'href' => $href, + 'type' => $type, + 'media' => $media, + 'element' => $element + ] + $id; + + return true; + } + + /** + * @param string|array $element + * @param int $priority + * @param string $location + * @return bool + */ + public function addInlineStyle($element, $priority = 0, $location = 'head') + { + if (!is_array($element)) { + $element = ['content' => (string) $element]; + } + if (empty($element['content'])) { + return false; + } + if (!isset($this->styles[$location])) { + $this->styles[$location] = []; + } + + $content = (string) $element['content']; + $type = !empty($element['type']) ? (string) $element['type'] : 'text/css'; + + unset($element['content'], $element['type']); + + $this->styles[$location][md5($content) . sha1($content)] = [ + ':type' => 'inline', + ':priority' => (int) $priority, + 'content' => $content, + 'type' => $type, + 'element' => $element + ]; + + return true; + } + + /** + * @param string|array $element + * @param int $priority + * @param string $location + * @return bool + */ + public function addScript($element, $priority = 0, $location = 'head') + { + if (!is_array($element)) { + $element = ['src' => (string) $element]; + } + if (empty($element['src'])) { + return false; + } + if (!isset($this->scripts[$location])) { + $this->scripts[$location] = []; + } + + $src = $element['src']; + $type = !empty($element['type']) ? (string) $element['type'] : 'text/javascript'; + $loading = !empty($element['loading']) ? (string) $element['loading'] : null; + $defer = !empty($element['defer']); + $async = !empty($element['async']); + $handle = !empty($element['handle']) ? (string) $element['handle'] : ''; + + unset($element['src'], $element['type'], $element['loading'], $element['defer'], $element['async'], $element['handle']); + + $this->scripts[$location][md5($src) . sha1($src)] = [ + ':type' => 'file', + ':priority' => (int) $priority, + 'src' => $src, + 'type' => $type, + 'loading' => $loading, + 'defer' => $defer, + 'async' => $async, + 'handle' => $handle, + 'element' => $element + ]; + + return true; + } + + /** + * @param string|array $element + * @param int $priority + * @param string $location + * @return bool + */ + public function addInlineScript($element, $priority = 0, $location = 'head') + { + if (!is_array($element)) { + $element = ['content' => (string) $element]; + } + if (empty($element['content'])) { + return false; + } + if (!isset($this->scripts[$location])) { + $this->scripts[$location] = []; + } + + $content = (string) $element['content']; + $type = !empty($element['type']) ? (string) $element['type'] : 'text/javascript'; + $loading = !empty($element['loading']) ? (string) $element['loading'] : null; + + unset($element['content'], $element['type'], $element['loading']); + + $this->scripts[$location][md5($content) . sha1($content)] = [ + ':type' => 'inline', + ':priority' => (int) $priority, + 'content' => $content, + 'type' => $type, + 'loading' => $loading, + 'element' => $element + ]; + + return true; + } + + /** + * @param string|array $element + * @param int $priority + * @param string $location + * @return bool + */ + public function addModule($element, $priority = 0, $location = 'head') + { + if (!is_array($element)) { + $element = ['src' => (string) $element]; + } + + $element['type'] = 'module'; + + return $this->addScript($element, $priority, $location); + } + + /** + * @param string|array $element + * @param int $priority + * @param string $location + * @return bool + */ + public function addInlineModule($element, $priority = 0, $location = 'head') + { + if (!is_array($element)) { + $element = ['content' => (string) $element]; + } + + $element['type'] = 'module'; + + return $this->addInlineScript($element, $priority, $location); + } + + /** + * @param array $element + * @param int $priority + * @param string $location + * @return bool + */ + public function addLink($element, $priority = 0, $location = 'head') + { + if (!is_array($element) || empty($element['rel']) || empty($element['href'])) { + return false; + } + + if (!isset($this->links[$location])) { + $this->links[$location] = []; + } + + $rel = (string) $element['rel']; + $href = (string) $element['href']; + + unset($element['rel'], $element['href']); + + $this->links[$location][md5($href) . sha1($href)] = [ + ':type' => 'file', + ':priority' => (int) $priority, + 'href' => $href, + 'rel' => $rel, + 'element' => $element, + ]; + + return true; + } + + /** + * @param string $html + * @param int $priority + * @param string $location + * @return bool + */ + public function addHtml($html, $priority = 0, $location = 'bottom') + { + if (empty($html) || !is_string($html)) { + return false; + } + if (!isset($this->html[$location])) { + $this->html[$location] = []; + } + + $this->html[$location][md5($html) . sha1($html)] = [ + ':priority' => (int) $priority, + 'html' => $html + ]; + + return true; + } + + /** + * @return array + */ + protected function getAssetsFast() + { + $assets = [ + 'frameworks' => $this->frameworks, + 'styles' => $this->styles, + 'scripts' => $this->scripts, + 'links' => $this->links, + 'html' => $this->html + ]; + + foreach ($this->blocks as $block) { + if ($block instanceof self) { + $blockAssets = $block->getAssetsFast(); + $assets['frameworks'] += $blockAssets['frameworks']; + + foreach ($blockAssets['styles'] as $location => $styles) { + if (!isset($assets['styles'][$location])) { + $assets['styles'][$location] = $styles; + } elseif ($styles) { + $assets['styles'][$location] += $styles; + } + } + + foreach ($blockAssets['scripts'] as $location => $scripts) { + if (!isset($assets['scripts'][$location])) { + $assets['scripts'][$location] = $scripts; + } elseif ($scripts) { + $assets['scripts'][$location] += $scripts; + } + } + + foreach ($blockAssets['links'] as $location => $links) { + if (!isset($assets['links'][$location])) { + $assets['links'][$location] = $links; + } elseif ($links) { + $assets['links'][$location] += $links; + } + } + + foreach ($blockAssets['html'] as $location => $htmls) { + if (!isset($assets['html'][$location])) { + $assets['html'][$location] = $htmls; + } elseif ($htmls) { + $assets['html'][$location] += $htmls; + } + } + } + } + + return $assets; + } + + /** + * @param string $type + * @param string $location + * @return array + */ + protected function getAssetsInLocation($type, $location) + { + $assets = $this->getAssetsFast(); + + if (empty($assets[$type][$location])) { + return []; + } + + $styles = $assets[$type][$location]; + $this->sortAssetsInLocation($styles); + + return $styles; + } + + /** + * @param array $items + * @return void + */ + protected function sortAssetsInLocation(array &$items) + { + $count = 0; + foreach ($items as &$item) { + $item[':order'] = ++$count; + } + unset($item); + + uasort( + $items, + static function ($a, $b) { + return $a[':priority'] <=> $b[':priority'] ?: $a[':order'] <=> $b[':order']; + } + ); + } + + /** + * @param array $array + * @return void + */ + protected function sortAssets(array &$array) + { + foreach ($array as &$items) { + $this->sortAssetsInLocation($items); + } + } +} diff --git a/system/src/Grav/Framework/ContentBlock/HtmlBlockInterface.php b/system/src/Grav/Framework/ContentBlock/HtmlBlockInterface.php new file mode 100644 index 0000000..833c140 --- /dev/null +++ b/system/src/Grav/Framework/ContentBlock/HtmlBlockInterface.php @@ -0,0 +1,130 @@ +addStyle('assets/js/my.js'); + * @example $block->addStyle(['href' => 'assets/js/my.js', 'media' => 'screen']); + */ + public function addStyle($element, $priority = 0, $location = 'head'); + + /** + * @param string|array $element + * @param int $priority + * @param string $location + * @return bool + */ + public function addInlineStyle($element, $priority = 0, $location = 'head'); + + /** + * @param string|array $element + * @param int $priority + * @param string $location + * @return bool + */ + public function addScript($element, $priority = 0, $location = 'head'); + + /** + * @param string|array $element + * @param int $priority + * @param string $location + * @return bool + */ + public function addInlineScript($element, $priority = 0, $location = 'head'); + + + /** + * Shortcut for writing addScript(['type' => 'module', 'src' => ...]). + * + * @param string|array $element + * @param int $priority + * @param string $location + * @return bool + */ + public function addModule($element, $priority = 0, $location = 'head'); + + /** + * Shortcut for writing addInlineScript(['type' => 'module', 'content' => ...]). + * + * @param string|array $element + * @param int $priority + * @param string $location + * @return bool + */ + public function addInlineModule($element, $priority = 0, $location = 'head'); + + /** + * @param array $element + * @param int $priority + * @param string $location + * @return bool + */ + public function addLink($element, $priority = 0, $location = 'head'); + + /** + * @param string $html + * @param int $priority + * @param string $location + * @return bool + */ + public function addHtml($html, $priority = 0, $location = 'bottom'); +} diff --git a/system/src/Grav/Framework/Contracts/Media/MediaObjectInterface.php b/system/src/Grav/Framework/Contracts/Media/MediaObjectInterface.php new file mode 100644 index 0000000..75b80f0 --- /dev/null +++ b/system/src/Grav/Framework/Contracts/Media/MediaObjectInterface.php @@ -0,0 +1,52 @@ +|ArrayAccess + * @phpstan-pure + */ + public function getIdentifierMeta(); +} diff --git a/system/src/Grav/Framework/Contracts/Relationships/RelationshipInterface.php b/system/src/Grav/Framework/Contracts/Relationships/RelationshipInterface.php new file mode 100644 index 0000000..c0a7edf --- /dev/null +++ b/system/src/Grav/Framework/Contracts/Relationships/RelationshipInterface.php @@ -0,0 +1,81 @@ + + */ +interface RelationshipInterface extends Countable, IteratorAggregate, JsonSerializable, Serializable +{ + /** + * @return string + * @phpstan-pure + */ + public function getName(): string; + + /** + * @return string + * @phpstan-pure + */ + public function getType(): string; + + /** + * @return bool + * @phpstan-pure + */ + public function isModified(): bool; + + /** + * @return string + * @phpstan-pure + */ + public function getCardinality(): string; + + /** + * @return P + * @phpstan-pure + */ + public function getParent(): IdentifierInterface; + + /** + * @param string $id + * @param string|null $type + * @return bool + * @phpstan-pure + */ + public function has(string $id, string $type = null): bool; + + /** + * @param T $identifier + * @return bool + * @phpstan-pure + */ + public function hasIdentifier(IdentifierInterface $identifier): bool; + + /** + * @param T $identifier + * @return bool + */ + public function addIdentifier(IdentifierInterface $identifier): bool; + + /** + * @param T|null $identifier + * @return bool + */ + public function removeIdentifier(IdentifierInterface $identifier = null): bool; + + /** + * @return iterable + */ + public function getIterator(): iterable; +} diff --git a/system/src/Grav/Framework/Contracts/Relationships/RelationshipsInterface.php b/system/src/Grav/Framework/Contracts/Relationships/RelationshipsInterface.php new file mode 100644 index 0000000..4bd90a3 --- /dev/null +++ b/system/src/Grav/Framework/Contracts/Relationships/RelationshipsInterface.php @@ -0,0 +1,53 @@ +> + * @extends Iterator> + */ +interface RelationshipsInterface extends Countable, ArrayAccess, Iterator, JsonSerializable +{ + /** + * @return bool + * @phpstan-pure + */ + public function isModified(): bool; + + /** + * @return array + */ + public function getModified(): array; + + /** + * @return int + * @phpstan-pure + */ + public function count(): int; + + /** + * @param string $offset + * @return RelationshipInterface|null + */ + public function offsetGet($offset): ?RelationshipInterface; + + /** + * @return RelationshipInterface|null + */ + public function current(): ?RelationshipInterface; + + /** + * @return string + * @phpstan-pure + */ + public function key(): string; +} diff --git a/system/src/Grav/Framework/Contracts/Relationships/ToManyRelationshipInterface.php b/system/src/Grav/Framework/Contracts/Relationships/ToManyRelationshipInterface.php new file mode 100644 index 0000000..723bef6 --- /dev/null +++ b/system/src/Grav/Framework/Contracts/Relationships/ToManyRelationshipInterface.php @@ -0,0 +1,55 @@ + + */ +interface ToManyRelationshipInterface extends RelationshipInterface +{ + /** + * @param positive-int $pos + * @return IdentifierInterface|null + */ + public function getNthIdentifier(int $pos): ?IdentifierInterface; + + /** + * @param string $id + * @param string|null $type + * @return T|null + * @phpstan-pure + */ + public function getIdentifier(string $id, string $type = null): ?IdentifierInterface; + + /** + * @param string $id + * @param string|null $type + * @return T|null + * @phpstan-pure + */ + public function getObject(string $id, string $type = null): ?object; + + /** + * @param iterable $identifiers + * @return bool + */ + public function addIdentifiers(iterable $identifiers): bool; + + /** + * @param iterable $identifiers + * @return bool + */ + public function replaceIdentifiers(iterable $identifiers): bool; + + /** + * @param iterable $identifiers + * @return bool + */ + public function removeIdentifiers(iterable $identifiers): bool; +} diff --git a/system/src/Grav/Framework/Contracts/Relationships/ToOneRelationshipInterface.php b/system/src/Grav/Framework/Contracts/Relationships/ToOneRelationshipInterface.php new file mode 100644 index 0000000..0e6aeb9 --- /dev/null +++ b/system/src/Grav/Framework/Contracts/Relationships/ToOneRelationshipInterface.php @@ -0,0 +1,37 @@ + + */ +interface ToOneRelationshipInterface extends RelationshipInterface +{ + /** + * @param string|null $id + * @param string|null $type + * @return T|null + * @phpstan-pure + */ + public function getIdentifier(string $id = null, string $type = null): ?IdentifierInterface; + + /** + * @param string|null $id + * @param string|null $type + * @return T|null + * @phpstan-pure + */ + public function getObject(string $id = null, string $type = null): ?object; + + /** + * @param T|null $identifier + * @return bool + */ + public function replaceIdentifier(IdentifierInterface $identifier = null): bool; +} diff --git a/system/src/Grav/Framework/Controller/Traits/ControllerResponseTrait.php b/system/src/Grav/Framework/Controller/Traits/ControllerResponseTrait.php new file mode 100644 index 0000000..02d8eab --- /dev/null +++ b/system/src/Grav/Framework/Controller/Traits/ControllerResponseTrait.php @@ -0,0 +1,307 @@ + 599) { + $code = 500; + } + $headers = $headers ?? []; + + return new Response($code, $headers, $content); + } + + /** + * @param array $content + * @param int|null $code + * @param array|null $headers + * @return Response + */ + protected function createJsonResponse(array $content, int $code = null, array $headers = null): ResponseInterface + { + $code = $code ?? $content['code'] ?? 200; + if (null === $code || $code < 100 || $code > 599) { + $code = 200; + } + $headers = ($headers ?? []) + [ + 'Content-Type' => 'application/json', + 'Cache-Control' => 'no-store, max-age=0' + ]; + + return new Response($code, $headers, json_encode($content)); + } + + /** + * @param string $filename + * @param string|resource|StreamInterface $resource + * @param array|null $headers + * @param array|null $options + * @return ResponseInterface + */ + protected function createDownloadResponse(string $filename, $resource, array $headers = null, array $options = null): ResponseInterface + { + // Required for IE, otherwise Content-Disposition may be ignored + if (ini_get('zlib.output_compression')) { + @ini_set('zlib.output_compression', 'Off'); + } + + $headers = $headers ?? []; + $options = $options ?? ['force_download' => true]; + + $file_parts = Utils::pathinfo($filename); + + if (!isset($headers['Content-Type'])) { + $mimetype = Utils::getMimeByExtension($file_parts['extension']); + + $headers['Content-Type'] = $mimetype; + } + + // TODO: add multipart download support. + //$headers['Accept-Ranges'] = 'bytes'; + + if (!empty($options['force_download'])) { + $headers['Content-Disposition'] = 'attachment; filename="' . $file_parts['basename'] . '"'; + } + + if (!isset($headers['Content-Length'])) { + $realpath = realpath($filename); + if ($realpath) { + $headers['Content-Length'] = filesize($realpath); + } + } + + $headers += [ + 'Expires' => 'Mon, 26 Jul 1997 05:00:00 GMT', + 'Last-Modified' => gmdate('D, d M Y H:i:s') . ' GMT', + 'Cache-Control' => 'no-store, no-cache, must-revalidate', + 'Pragma' => 'no-cache' + ]; + + return new Response(200, $headers, $resource); + } + + /** + * @param string $url + * @param int|null $code + * @return Response + */ + protected function createRedirectResponse(string $url, int $code = null): ResponseInterface + { + if (null === $code || $code < 301 || $code > 307) { + $code = (int)$this->getConfig()->get('system.pages.redirect_default_code', 302); + } + + $ext = Utils::pathinfo($url, PATHINFO_EXTENSION); + $accept = $this->getAccept(['application/json', 'text/html']); + if ($ext === 'json' || $accept === 'application/json') { + return $this->createJsonResponse(['code' => $code, 'status' => 'redirect', 'redirect' => $url]); + } + + return new Response($code, ['Location' => $url]); + } + + /** + * @param Throwable $e + * @return ResponseInterface + */ + protected function createErrorResponse(Throwable $e): ResponseInterface + { + $response = $this->getErrorJson($e); + $message = $response['message']; + $code = $response['code']; + $reason = $e instanceof RequestException ? $e->getHttpReason() : null; + $accept = $this->getAccept(['application/json', 'text/html']); + + $request = $this->getRequest(); + $context = $request->getAttributes(); + + /** @var Route $route */ + $route = $context['route'] ?? null; + + $ext = $route ? $route->getExtension() : null; + if ($ext !== 'json' && $accept === 'text/html') { + $method = $request->getMethod(); + + // On POST etc, redirect back to the previous page. + if ($method !== 'GET' && $method !== 'HEAD') { + $this->setMessage($message, 'error'); + $referer = $request->getHeaderLine('Referer'); + + return $this->createRedirectResponse($referer, 303); + } + + // TODO: improve error page + return $this->createHtmlResponse($response['message'], $code); + } + + return new Response($code, ['Content-Type' => 'application/json'], json_encode($response), '1.1', $reason); + } + + /** + * @param Throwable $e + * @return ResponseInterface + */ + protected function createJsonErrorResponse(Throwable $e): ResponseInterface + { + $response = $this->getErrorJson($e); + $reason = $e instanceof RequestException ? $e->getHttpReason() : null; + + return new Response($response['code'], ['Content-Type' => 'application/json'], json_encode($response), '1.1', $reason); + } + + /** + * @param Throwable $e + * @return array + */ + protected function getErrorJson(Throwable $e): array + { + $code = $this->getErrorCode($e instanceof RequestException ? $e->getHttpCode() : $e->getCode()); + if ($e instanceof ValidationException) { + $message = $e->getMessage(); + } else { + $message = htmlspecialchars($e->getMessage(), ENT_QUOTES | ENT_HTML5, 'UTF-8'); + } + + $extra = $e instanceof JsonSerializable ? $e->jsonSerialize() : []; + + $response = [ + 'code' => $code, + 'status' => 'error', + 'message' => $message, + 'redirect' => null, + 'error' => [ + 'code' => $code, + 'message' => $message + ] + $extra + ]; + + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + if ($debugger->enabled()) { + $response['error'] += [ + 'type' => get_class($e), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'trace' => explode("\n", $e->getTraceAsString()) + ]; + } + + return $response; + } + + /** + * @param int $code + * @return int + */ + protected function getErrorCode(int $code): int + { + static $errorCodes = [ + 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, + 422, 423, 424, 425, 426, 428, 429, 431, 451, 500, 501, 502, 503, 504, 505, 506, 507, 508, 511 + ]; + + if (!in_array($code, $errorCodes, true)) { + $code = 500; + } + + return $code; + } + + /** + * @param array $compare + * @return mixed + */ + protected function getAccept(array $compare) + { + $accepted = []; + foreach ($this->getRequest()->getHeader('Accept') as $accept) { + foreach (explode(',', $accept) as $item) { + if (!$item) { + continue; + } + + $split = explode(';q=', $item); + $mime = array_shift($split); + $priority = array_shift($split) ?? 1.0; + + $accepted[$mime] = $priority; + } + } + + arsort($accepted); + + // TODO: add support for image/* etc + $list = array_intersect($compare, array_keys($accepted)); + if (!$list && (isset($accepted['*/*']) || isset($accepted['*']))) { + return reset($compare); + } + + return reset($list); + } + + /** + * @return ServerRequestInterface + */ + abstract protected function getRequest(): ServerRequestInterface; + + /** + * @param string $message + * @param string $type + * @return $this + */ + abstract protected function setMessage(string $message, string $type = 'info'); + + /** + * @return Config + */ + abstract protected function getConfig(): Config; +} diff --git a/system/src/Grav/Framework/DI/Container.php b/system/src/Grav/Framework/DI/Container.php new file mode 100644 index 0000000..a4aefa4 --- /dev/null +++ b/system/src/Grav/Framework/DI/Container.php @@ -0,0 +1,35 @@ +offsetGet($id); + } + + /** + * @param string $id + * @return bool + */ + public function has($id): bool + { + return $this->offsetExists($id); + } +} diff --git a/system/src/Grav/Framework/File/AbstractFile.php b/system/src/Grav/Framework/File/AbstractFile.php new file mode 100644 index 0000000..b4d3840 --- /dev/null +++ b/system/src/Grav/Framework/File/AbstractFile.php @@ -0,0 +1,444 @@ +filesystem = $filesystem ?? Filesystem::getInstance(); + $this->setFilepath($filepath); + } + + /** + * Unlock file when the object gets destroyed. + */ + #[\ReturnTypeWillChange] + public function __destruct() + { + if ($this->isLocked()) { + $this->unlock(); + } + } + + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function __clone() + { + $this->handle = null; + $this->locked = false; + } + + /** + * @return array + */ + final public function __serialize(): array + { + return ['filesystem_normalize' => $this->filesystem->getNormalization()] + $this->doSerialize(); + } + + /** + * @param array $data + * @return void + */ + final public function __unserialize(array $data): void + { + $this->filesystem = Filesystem::getInstance($data['filesystem_normalize'] ?? null); + + $this->doUnserialize($data); + } + + /** + * {@inheritdoc} + * @see FileInterface::getFilePath() + */ + public function getFilePath(): string + { + return $this->filepath; + } + + /** + * {@inheritdoc} + * @see FileInterface::getPath() + */ + public function getPath(): string + { + if (null === $this->path) { + $this->setPathInfo(); + } + + return $this->path ?? ''; + } + + /** + * {@inheritdoc} + * @see FileInterface::getFilename() + */ + public function getFilename(): string + { + if (null === $this->filename) { + $this->setPathInfo(); + } + + return $this->filename ?? ''; + } + + /** + * {@inheritdoc} + * @see FileInterface::getBasename() + */ + public function getBasename(): string + { + if (null === $this->basename) { + $this->setPathInfo(); + } + + return $this->basename ?? ''; + } + + /** + * {@inheritdoc} + * @see FileInterface::getExtension() + */ + public function getExtension(bool $withDot = false): string + { + if (null === $this->extension) { + $this->setPathInfo(); + } + + return ($withDot ? '.' : '') . $this->extension; + } + + /** + * {@inheritdoc} + * @see FileInterface::exists() + */ + public function exists(): bool + { + return is_file($this->filepath); + } + + /** + * {@inheritdoc} + * @see FileInterface::getCreationTime() + */ + public function getCreationTime(): int + { + return is_file($this->filepath) ? (int)filectime($this->filepath) : time(); + } + + /** + * {@inheritdoc} + * @see FileInterface::getModificationTime() + */ + public function getModificationTime(): int + { + return is_file($this->filepath) ? (int)filemtime($this->filepath) : time(); + } + + /** + * {@inheritdoc} + * @see FileInterface::lock() + */ + public function lock(bool $block = true): bool + { + if (!$this->handle) { + if (!$this->mkdir($this->getPath())) { + throw new RuntimeException('Creating directory failed for ' . $this->filepath); + } + $this->handle = @fopen($this->filepath, 'cb+') ?: null; + if (!$this->handle) { + $error = error_get_last(); + $message = $error['message'] ?? 'Unknown error'; + + throw new RuntimeException("Opening file for writing failed on error {$message}"); + } + } + + $lock = $block ? LOCK_EX : LOCK_EX | LOCK_NB; + + // Some filesystems do not support file locks, only fail if another process holds the lock. + $this->locked = flock($this->handle, $lock, $wouldBlock) || !$wouldBlock; + + return $this->locked; + } + + /** + * {@inheritdoc} + * @see FileInterface::unlock() + */ + public function unlock(): bool + { + if (!$this->handle) { + return false; + } + + if ($this->locked) { + flock($this->handle, LOCK_UN | LOCK_NB); + $this->locked = false; + } + + fclose($this->handle); + $this->handle = null; + + return true; + } + + /** + * {@inheritdoc} + * @see FileInterface::isLocked() + */ + public function isLocked(): bool + { + return $this->locked; + } + + /** + * {@inheritdoc} + * @see FileInterface::isReadable() + */ + public function isReadable(): bool + { + return is_readable($this->filepath) && is_file($this->filepath); + } + + /** + * {@inheritdoc} + * @see FileInterface::isWritable() + */ + public function isWritable(): bool + { + if (!file_exists($this->filepath)) { + return $this->isWritablePath($this->getPath()); + } + + return is_writable($this->filepath) && is_file($this->filepath); + } + + /** + * {@inheritdoc} + * @see FileInterface::load() + */ + public function load() + { + return file_get_contents($this->filepath); + } + + /** + * {@inheritdoc} + * @see FileInterface::save() + */ + public function save($data): void + { + $filepath = $this->filepath; + $dir = $this->getPath(); + + if (!$this->mkdir($dir)) { + throw new RuntimeException('Creating directory failed for ' . $filepath); + } + + try { + if ($this->handle) { + $tmp = true; + // As we are using non-truncating locking, make sure that the file is empty before writing. + if (@ftruncate($this->handle, 0) === false || @fwrite($this->handle, $data) === false) { + // Writing file failed, throw an error. + $tmp = false; + } + } else { + // Support for symlinks. + $realpath = is_link($filepath) ? realpath($filepath) : $filepath; + if ($realpath === false) { + throw new RuntimeException('Failed to save file ' . $filepath); + } + + // Create file with a temporary name and rename it to make the save action atomic. + $tmp = $this->tempname($realpath); + if (@file_put_contents($tmp, $data) === false) { + $tmp = false; + } elseif (@rename($tmp, $realpath) === false) { + @unlink($tmp); + $tmp = false; + } + } + } catch (Exception $e) { + $tmp = false; + } + + if ($tmp === false) { + throw new RuntimeException('Failed to save file ' . $filepath); + } + + // Touch the directory as well, thus marking it modified. + @touch($dir); + } + + /** + * {@inheritdoc} + * @see FileInterface::rename() + */ + public function rename(string $path): bool + { + if ($this->exists() && !@rename($this->filepath, $path)) { + return false; + } + + $this->setFilepath($path); + + return true; + } + + /** + * {@inheritdoc} + * @see FileInterface::delete() + */ + public function delete(): bool + { + return @unlink($this->filepath); + } + + /** + * @param string $dir + * @return bool + * @throws RuntimeException + * @internal + */ + protected function mkdir(string $dir): bool + { + // Silence error for open_basedir; should fail in mkdir instead. + if (@is_dir($dir)) { + return true; + } + + $success = @mkdir($dir, 0777, true); + + if (!$success) { + // Take yet another look, make sure that the folder doesn't exist. + clearstatcache(true, $dir); + if (!@is_dir($dir)) { + return false; + } + } + + return true; + } + + /** + * @return array + */ + protected function doSerialize(): array + { + return [ + 'filepath' => $this->filepath + ]; + } + + /** + * @param array $serialized + * @return void + */ + protected function doUnserialize(array $serialized): void + { + $this->setFilepath($serialized['filepath']); + } + + /** + * @param string $filepath + */ + protected function setFilepath(string $filepath): void + { + $this->filepath = $filepath; + $this->filename = null; + $this->basename = null; + $this->path = null; + $this->extension = null; + } + + protected function setPathInfo(): void + { + /** @var array $pathInfo */ + $pathInfo = $this->filesystem->pathinfo($this->filepath); + + $this->filename = $pathInfo['filename'] ?? null; + $this->basename = $pathInfo['basename'] ?? null; + $this->path = $pathInfo['dirname'] ?? null; + $this->extension = $pathInfo['extension'] ?? null; + } + + /** + * @param string $dir + * @return bool + * @internal + */ + protected function isWritablePath(string $dir): bool + { + if ($dir === '') { + return false; + } + + if (!file_exists($dir)) { + // Recursively look up in the directory tree. + return $this->isWritablePath($this->filesystem->parent($dir)); + } + + return is_dir($dir) && is_writable($dir); + } + + /** + * @param string $filename + * @param int $length + * @return string + */ + protected function tempname(string $filename, int $length = 5) + { + do { + $test = $filename . substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, $length); + } while (file_exists($test)); + + return $test; + } +} diff --git a/system/src/Grav/Framework/File/CsvFile.php b/system/src/Grav/Framework/File/CsvFile.php new file mode 100644 index 0000000..4f41f72 --- /dev/null +++ b/system/src/Grav/Framework/File/CsvFile.php @@ -0,0 +1,40 @@ +formatter = $formatter; + } + + /** + * {@inheritdoc} + * @see FileInterface::load() + */ + public function load() + { + $raw = parent::load(); + + try { + if (!is_string($raw)) { + throw new RuntimeException('Bad Data'); + } + + return $this->formatter->decode($raw); + } catch (RuntimeException $e) { + throw new RuntimeException(sprintf("Failed to load file '%s': %s", $this->getFilePath(), $e->getMessage()), $e->getCode(), $e); + } + } + + /** + * {@inheritdoc} + * @see FileInterface::save() + */ + public function save($data): void + { + if (is_string($data)) { + // Make sure that the string is valid data. + try { + $this->formatter->decode($data); + } catch (RuntimeException $e) { + throw new RuntimeException(sprintf("Failed to save file '%s': %s", $this->getFilePath(), $e->getMessage()), $e->getCode(), $e); + } + $encoded = $data; + } else { + $encoded = $this->formatter->encode($data); + } + + parent::save($encoded); + } +} diff --git a/system/src/Grav/Framework/File/File.php b/system/src/Grav/Framework/File/File.php new file mode 100644 index 0000000..968a43c --- /dev/null +++ b/system/src/Grav/Framework/File/File.php @@ -0,0 +1,35 @@ +config = $config; + } + + /** + * @return string + */ + public function getMimeType(): string + { + $mime = $this->getConfig('mime'); + + return is_string($mime) ? $mime : 'application/octet-stream'; + } + + /** + * {@inheritdoc} + * @see FileFormatterInterface::getDefaultFileExtension() + */ + public function getDefaultFileExtension(): string + { + $extensions = $this->getSupportedFileExtensions(); + + // Call fails on bad configuration. + return reset($extensions) ?: ''; + } + + /** + * {@inheritdoc} + * @see FileFormatterInterface::getSupportedFileExtensions() + */ + public function getSupportedFileExtensions(): array + { + $extensions = $this->getConfig('file_extension'); + + // Call fails on bad configuration. + return is_string($extensions) ? [$extensions] : $extensions; + } + + /** + * {@inheritdoc} + * @see FileFormatterInterface::encode() + */ + abstract public function encode($data): string; + + /** + * {@inheritdoc} + * @see FileFormatterInterface::decode() + */ + abstract public function decode($data); + + + /** + * @return array + */ + public function __serialize(): array + { + return ['config' => $this->config]; + } + + /** + * @param array $data + * @return void + */ + public function __unserialize(array $data): void + { + $this->config = $data['config']; + } + + /** + * Get either full configuration or a single option. + * + * @param string|null $name Configuration option (optional) + * @return mixed + */ + protected function getConfig(string $name = null) + { + if (null !== $name) { + return $this->config[$name] ?? null; + } + + return $this->config; + } +} diff --git a/system/src/Grav/Framework/File/Formatter/CsvFormatter.php b/system/src/Grav/Framework/File/Formatter/CsvFormatter.php new file mode 100644 index 0000000..914b8f8 --- /dev/null +++ b/system/src/Grav/Framework/File/Formatter/CsvFormatter.php @@ -0,0 +1,170 @@ + ['.csv', '.tsv'], + 'delimiter' => ',', + 'mime' => 'text/x-csv' + ]; + + parent::__construct($config); + } + + /** + * Returns delimiter used to both encode and decode CSV. + * + * @return string + */ + public function getDelimiter(): string + { + // Call fails on bad configuration. + return $this->getConfig('delimiter'); + } + + /** + * @param array $data + * @param string|null $delimiter + * @return string + * @see FileFormatterInterface::encode() + */ + public function encode($data, $delimiter = null): string + { + if (count($data) === 0) { + return ''; + } + $delimiter = $delimiter ?? $this->getDelimiter(); + $header = array_keys(reset($data)); + + // Encode the field names + $string = $this->encodeLine($header, $delimiter); + + // Encode the data + foreach ($data as $row) { + $string .= $this->encodeLine($row, $delimiter); + } + + return $string; + } + + /** + * @param string $data + * @param string|null $delimiter + * @return array + * @see FileFormatterInterface::decode() + */ + public function decode($data, $delimiter = null): array + { + $delimiter = $delimiter ?? $this->getDelimiter(); + $lines = preg_split('/\r\n|\r|\n/', $data); + if ($lines === false) { + throw new RuntimeException('Decoding CSV failed'); + } + + // Get the field names + $headerStr = array_shift($lines); + if (!$headerStr) { + throw new RuntimeException('CSV header missing'); + } + + $header = str_getcsv($headerStr, $delimiter); + + // Allow for replacing a null string with null/empty value + $null_replace = $this->getConfig('null'); + + // Get the data + $list = []; + $line = null; + try { + foreach ($lines as $line) { + if (!empty($line)) { + $csv_line = str_getcsv($line, $delimiter); + + if ($null_replace) { + array_walk($csv_line, static function (&$el) use ($null_replace) { + $el = str_replace($null_replace, "\0", $el); + }); + } + + $list[] = array_combine($header, $csv_line); + } + } + } catch (Exception $e) { + throw new RuntimeException('Badly formatted CSV line: ' . $line); + } + + return $list; + } + + /** + * @param array $line + * @param string $delimiter + * @return string + */ + protected function encodeLine(array $line, string $delimiter): string + { + foreach ($line as $key => &$value) { + // Oops, we need to convert the line to a string. + if (!is_scalar($value)) { + if (is_array($value) || $value instanceof JsonSerializable || $value instanceof stdClass) { + $value = json_encode($value); + } elseif (is_object($value)) { + if (method_exists($value, 'toJson')) { + $value = $value->toJson(); + } elseif (method_exists($value, 'toArray')) { + $value = json_encode($value->toArray()); + } + } + } + + $value = $this->escape((string)$value); + } + unset($value); + + return implode($delimiter, $line). "\n"; + } + + /** + * @param string $value + * @return string + */ + protected function escape(string $value) + { + if (preg_match('/[,"\r\n]/u', $value)) { + $value = '"' . preg_replace('/"/', '""', $value) . '"'; + } + + return $value; + } +} diff --git a/system/src/Grav/Framework/File/Formatter/FormatterInterface.php b/system/src/Grav/Framework/File/Formatter/FormatterInterface.php new file mode 100644 index 0000000..757e229 --- /dev/null +++ b/system/src/Grav/Framework/File/Formatter/FormatterInterface.php @@ -0,0 +1,12 @@ + '.ini' + ]; + + parent::__construct($config); + } + + /** + * {@inheritdoc} + * @see FileFormatterInterface::encode() + */ + public function encode($data): string + { + $string = ''; + foreach ($data as $key => $value) { + $string .= $key . '="' . preg_replace( + ['/"/', '/\\\/', "/\t/", "/\n/", "/\r/"], + ['\"', '\\\\', '\t', '\n', '\r'], + $value + ) . "\"\n"; + } + + return $string; + } + + /** + * {@inheritdoc} + * @see FileFormatterInterface::decode() + */ + public function decode($data): array + { + $decoded = @parse_ini_string($data); + + if ($decoded === false) { + throw new RuntimeException('Decoding INI failed'); + } + + return $decoded; + } +} diff --git a/system/src/Grav/Framework/File/Formatter/JsonFormatter.php b/system/src/Grav/Framework/File/Formatter/JsonFormatter.php new file mode 100644 index 0000000..9c9b8a0 --- /dev/null +++ b/system/src/Grav/Framework/File/Formatter/JsonFormatter.php @@ -0,0 +1,170 @@ + JSON_FORCE_OBJECT, + 'JSON_HEX_QUOT' => JSON_HEX_QUOT, + 'JSON_HEX_TAG' => JSON_HEX_TAG, + 'JSON_HEX_AMP' => JSON_HEX_AMP, + 'JSON_HEX_APOS' => JSON_HEX_APOS, + 'JSON_INVALID_UTF8_IGNORE' => JSON_INVALID_UTF8_IGNORE, + 'JSON_INVALID_UTF8_SUBSTITUTE' => JSON_INVALID_UTF8_SUBSTITUTE, + 'JSON_NUMERIC_CHECK' => JSON_NUMERIC_CHECK, + 'JSON_PARTIAL_OUTPUT_ON_ERROR' => JSON_PARTIAL_OUTPUT_ON_ERROR, + 'JSON_PRESERVE_ZERO_FRACTION' => JSON_PRESERVE_ZERO_FRACTION, + 'JSON_PRETTY_PRINT' => JSON_PRETTY_PRINT, + 'JSON_UNESCAPED_LINE_TERMINATORS' => JSON_UNESCAPED_LINE_TERMINATORS, + 'JSON_UNESCAPED_SLASHES' => JSON_UNESCAPED_SLASHES, + 'JSON_UNESCAPED_UNICODE' => JSON_UNESCAPED_UNICODE, + //'JSON_THROW_ON_ERROR' => JSON_THROW_ON_ERROR // PHP 7.3 + ]; + + /** @var array */ + protected $decodeOptions = [ + 'JSON_BIGINT_AS_STRING' => JSON_BIGINT_AS_STRING, + 'JSON_INVALID_UTF8_IGNORE' => JSON_INVALID_UTF8_IGNORE, + 'JSON_INVALID_UTF8_SUBSTITUTE' => JSON_INVALID_UTF8_SUBSTITUTE, + 'JSON_OBJECT_AS_ARRAY' => JSON_OBJECT_AS_ARRAY, + //'JSON_THROW_ON_ERROR' => JSON_THROW_ON_ERROR // PHP 7.3 + ]; + + public function __construct(array $config = []) + { + $config += [ + 'file_extension' => '.json', + 'encode_options' => 0, + 'decode_assoc' => true, + 'decode_depth' => 512, + 'decode_options' => 0 + ]; + + parent::__construct($config); + } + + /** + * Returns options used in encode() function. + * + * @return int + */ + public function getEncodeOptions(): int + { + $options = $this->getConfig('encode_options'); + if (!is_int($options)) { + if (is_string($options)) { + $list = preg_split('/[\s,|]+/', $options); + $options = 0; + if ($list) { + foreach ($list as $option) { + if (isset($this->encodeOptions[$option])) { + $options += $this->encodeOptions[$option]; + } + } + } + } else { + $options = 0; + } + } + + return $options; + } + + /** + * Returns options used in decode() function. + * + * @return int + */ + public function getDecodeOptions(): int + { + $options = $this->getConfig('decode_options'); + if (!is_int($options)) { + if (is_string($options)) { + $list = preg_split('/[\s,|]+/', $options); + $options = 0; + if ($list) { + foreach ($list as $option) { + if (isset($this->decodeOptions[$option])) { + $options += $this->decodeOptions[$option]; + } + } + } + } else { + $options = 0; + } + } + + return $options; + } + + /** + * Returns recursion depth used in decode() function. + * + * @return int + * @phpstan-return positive-int + */ + public function getDecodeDepth(): int + { + return $this->getConfig('decode_depth'); + } + + /** + * Returns true if JSON objects will be converted into associative arrays. + * + * @return bool + */ + public function getDecodeAssoc(): bool + { + return $this->getConfig('decode_assoc'); + } + + /** + * {@inheritdoc} + * @see FileFormatterInterface::encode() + */ + public function encode($data): string + { + $encoded = @json_encode($data, $this->getEncodeOptions()); + + if ($encoded === false && json_last_error() !== JSON_ERROR_NONE) { + throw new RuntimeException('Encoding JSON failed: ' . json_last_error_msg()); + } + + return $encoded ?: ''; + } + + /** + * {@inheritdoc} + * @see FileFormatterInterface::decode() + */ + public function decode($data) + { + $decoded = @json_decode($data, $this->getDecodeAssoc(), $this->getDecodeDepth(), $this->getDecodeOptions()); + + if (null === $decoded && json_last_error() !== JSON_ERROR_NONE) { + throw new RuntimeException('Decoding JSON failed: ' . json_last_error_msg()); + } + + return $decoded; + } +} diff --git a/system/src/Grav/Framework/File/Formatter/MarkdownFormatter.php b/system/src/Grav/Framework/File/Formatter/MarkdownFormatter.php new file mode 100644 index 0000000..e12b71f --- /dev/null +++ b/system/src/Grav/Framework/File/Formatter/MarkdownFormatter.php @@ -0,0 +1,161 @@ + '.md', + 'header' => 'header', + 'body' => 'markdown', + 'raw' => 'frontmatter', + 'yaml' => ['inline' => 20] + ]; + + parent::__construct($config); + + $this->headerFormatter = $headerFormatter ?? new YamlFormatter($config['yaml']); + } + + /** + * Returns header field used in both encode() and decode(). + * + * @return string + */ + public function getHeaderField(): string + { + return $this->getConfig('header'); + } + + /** + * Returns body field used in both encode() and decode(). + * + * @return string + */ + public function getBodyField(): string + { + return $this->getConfig('body'); + } + + /** + * Returns raw field used in both encode() and decode(). + * + * @return string + */ + public function getRawField(): string + { + return $this->getConfig('raw'); + } + + /** + * Returns header formatter object used in both encode() and decode(). + * + * @return FileFormatterInterface + */ + public function getHeaderFormatter(): FileFormatterInterface + { + return $this->headerFormatter; + } + + /** + * {@inheritdoc} + * @see FileFormatterInterface::encode() + */ + public function encode($data): string + { + $headerVar = $this->getHeaderField(); + $bodyVar = $this->getBodyField(); + + $header = isset($data[$headerVar]) ? (array) $data[$headerVar] : []; + $body = isset($data[$bodyVar]) ? (string) $data[$bodyVar] : ''; + + // Create Markdown file with YAML header. + $encoded = ''; + if ($header) { + $encoded = "---\n" . trim($this->getHeaderFormatter()->encode($data['header'])) . "\n---\n\n"; + } + $encoded .= $body; + + // Normalize line endings to Unix style. + $encoded = preg_replace("/(\r\n|\r)/u", "\n", $encoded); + if (null === $encoded) { + throw new RuntimeException('Encoding markdown failed'); + } + + return $encoded; + } + + /** + * {@inheritdoc} + * @see FileFormatterInterface::decode() + */ + public function decode($data): array + { + $headerVar = $this->getHeaderField(); + $bodyVar = $this->getBodyField(); + $rawVar = $this->getRawField(); + + // Define empty content + $content = [ + $headerVar => [], + $bodyVar => '' + ]; + + $headerRegex = "/^---\n(.+?)\n---\n{0,}(.*)$/uis"; + + // Normalize line endings to Unix style. + $data = preg_replace("/(\r\n|\r)/u", "\n", $data); + if (null === $data) { + throw new RuntimeException('Decoding markdown failed'); + } + + // Parse header. + preg_match($headerRegex, ltrim($data), $matches); + if (empty($matches)) { + $content[$bodyVar] = $data; + } else { + // Normalize frontmatter. + $frontmatter = preg_replace("/\n\t/", "\n ", $matches[1]); + if ($rawVar) { + $content[$rawVar] = $frontmatter; + } + $content[$headerVar] = $this->getHeaderFormatter()->decode($frontmatter); + $content[$bodyVar] = $matches[2]; + } + + return $content; + } + + public function __serialize(): array + { + return parent::__serialize() + ['headerFormatter' => $this->headerFormatter]; + } + + public function __unserialize(array $data): void + { + parent::__unserialize($data); + + $this->headerFormatter = $data['headerFormatter'] ?? new YamlFormatter(['inline' => 20]); + } +} diff --git a/system/src/Grav/Framework/File/Formatter/SerializeFormatter.php b/system/src/Grav/Framework/File/Formatter/SerializeFormatter.php new file mode 100644 index 0000000..76ff2b1 --- /dev/null +++ b/system/src/Grav/Framework/File/Formatter/SerializeFormatter.php @@ -0,0 +1,98 @@ + '.ser', + 'decode_options' => ['allowed_classes' => [stdClass::class]] + ]; + + parent::__construct($config); + } + + /** + * Returns options used in decode(). + * + * By default only allow stdClass class. + * + * @return array + */ + public function getOptions() + { + return $this->getConfig('decode_options'); + } + + /** + * {@inheritdoc} + * @see FileFormatterInterface::encode() + */ + public function encode($data): string + { + return serialize($this->preserveLines($data, ["\n", "\r"], ['\\n', '\\r'])); + } + + /** + * {@inheritdoc} + * @see FileFormatterInterface::decode() + */ + public function decode($data) + { + $classes = $this->getOptions()['allowed_classes'] ?? false; + $decoded = @unserialize($data, ['allowed_classes' => $classes]); + + if ($decoded === false && $data !== serialize(false)) { + throw new RuntimeException('Decoding serialized data failed'); + } + + return $this->preserveLines($decoded, ['\\n', '\\r'], ["\n", "\r"]); + } + + /** + * Preserve new lines, recursive function. + * + * @param mixed $data + * @param array $search + * @param array $replace + * @return mixed + */ + protected function preserveLines($data, array $search, array $replace) + { + if (is_string($data)) { + $data = str_replace($search, $replace, $data); + } elseif (is_array($data)) { + foreach ($data as &$value) { + $value = $this->preserveLines($value, $search, $replace); + } + unset($value); + } + + return $data; + } +} diff --git a/system/src/Grav/Framework/File/Formatter/YamlFormatter.php b/system/src/Grav/Framework/File/Formatter/YamlFormatter.php new file mode 100644 index 0000000..08a6f72 --- /dev/null +++ b/system/src/Grav/Framework/File/Formatter/YamlFormatter.php @@ -0,0 +1,129 @@ + '.yaml', + 'inline' => 5, + 'indent' => 2, + 'native' => true, + 'compat' => true + ]; + + parent::__construct($config); + } + + /** + * @return int + */ + public function getInlineOption(): int + { + return $this->getConfig('inline'); + } + + /** + * @return int + */ + public function getIndentOption(): int + { + return $this->getConfig('indent'); + } + + /** + * @return bool + */ + public function useNativeDecoder(): bool + { + return $this->getConfig('native'); + } + + /** + * @return bool + */ + public function useCompatibleDecoder(): bool + { + return $this->getConfig('compat'); + } + + /** + * @param array $data + * @param int|null $inline + * @param int|null $indent + * @return string + * @see FileFormatterInterface::encode() + */ + public function encode($data, $inline = null, $indent = null): string + { + try { + return YamlParser::dump( + $data, + $inline ? (int) $inline : $this->getInlineOption(), + $indent ? (int) $indent : $this->getIndentOption(), + YamlParser::DUMP_EXCEPTION_ON_INVALID_TYPE + ); + } catch (DumpException $e) { + throw new RuntimeException('Encoding YAML failed: ' . $e->getMessage(), 0, $e); + } + } + + /** + * {@inheritdoc} + * @see FileFormatterInterface::decode() + */ + public function decode($data): array + { + // Try native PECL YAML PHP extension first if available. + if (function_exists('yaml_parse') && $this->useNativeDecoder()) { + // Safely decode YAML. + $saved = @ini_get('yaml.decode_php'); + @ini_set('yaml.decode_php', '0'); + $decoded = @yaml_parse($data); + if ($saved !== false) { + @ini_set('yaml.decode_php', $saved); + } + + if ($decoded !== false) { + return (array) $decoded; + } + } + + try { + return (array) YamlParser::parse($data); + } catch (ParseException $e) { + if ($this->useCompatibleDecoder()) { + return (array) FallbackYamlParser::parse($data); + } + + throw new RuntimeException('Decoding YAML failed: ' . $e->getMessage(), 0, $e); + } + } +} diff --git a/system/src/Grav/Framework/File/IniFile.php b/system/src/Grav/Framework/File/IniFile.php new file mode 100644 index 0000000..a50641c --- /dev/null +++ b/system/src/Grav/Framework/File/IniFile.php @@ -0,0 +1,40 @@ +setNormalization() + * @return Filesystem + */ + public static function getInstance(bool $normalize = null): Filesystem + { + if ($normalize === true) { + $instance = &static::$safe; + } elseif ($normalize === false) { + $instance = &static::$unsafe; + } else { + $instance = &static::$default; + } + + if (null === $instance) { + $instance = new static($normalize); + } + + return $instance; + } + + /** + * Always use Filesystem::getInstance() instead. + * + * @param bool|null $normalize + * @internal + */ + protected function __construct(bool $normalize = null) + { + $this->normalize = $normalize; + } + + /** + * Set path normalization. + * + * Default option enables normalization for the streams only, but you can force the normalization to be either + * on or off for every path. Disabling path normalization speeds up the calls, but may cause issues if paths were + * not normalized. + * + * @param bool|null $normalize + * @return Filesystem + */ + public function setNormalization(bool $normalize = null): self + { + return static::getInstance($normalize); + } + + /** + * @return bool|null + */ + public function getNormalization(): ?bool + { + return $this->normalize; + } + + /** + * Force all paths to be normalized. + * + * @return self + */ + public function unsafe(): self + { + return static::getInstance(true); + } + + /** + * Force all paths not to be normalized (speeds up the calls if given paths are known to be normalized). + * + * @return self + */ + public function safe(): self + { + return static::getInstance(false); + } + + /** + * {@inheritdoc} + * @see FilesystemInterface::parent() + */ + public function parent(string $path, int $levels = 1): string + { + [$scheme, $path] = $this->getSchemeAndHierarchy($path); + + if ($this->normalize !== false) { + $path = $this->normalizePathPart($path); + } + + if ($path === '' || $path === '.') { + return ''; + } + + [$scheme, $parent] = $this->dirnameInternal($scheme, $path, $levels); + + return $parent !== $path ? $this->toString($scheme, $parent) : ''; + } + + /** + * {@inheritdoc} + * @see FilesystemInterface::normalize() + */ + public function normalize(string $path): string + { + [$scheme, $path] = $this->getSchemeAndHierarchy($path); + + $path = $this->normalizePathPart($path); + + return $this->toString($scheme, $path); + } + + /** + * {@inheritdoc} + * @see FilesystemInterface::basename() + */ + public function basename(string $path, ?string $suffix = null): string + { + // Escape path. + $path = str_replace(['%2F', '%5C'], '/', rawurlencode($path)); + + return rawurldecode($suffix ? basename($path, $suffix) : basename($path)); + } + + /** + * {@inheritdoc} + * @see FilesystemInterface::dirname() + */ + public function dirname(string $path, int $levels = 1): string + { + [$scheme, $path] = $this->getSchemeAndHierarchy($path); + + if ($this->normalize || ($scheme && null === $this->normalize)) { + $path = $this->normalizePathPart($path); + } + + [$scheme, $path] = $this->dirnameInternal($scheme, $path, $levels); + + return $this->toString($scheme, $path); + } + + /** + * Gets full path with trailing slash. + * + * @param string $path + * @param int $levels + * @return string + * @phpstan-param positive-int $levels + */ + public function pathname(string $path, int $levels = 1): string + { + $path = $this->dirname($path, $levels); + + return $path !== '.' ? $path . '/' : ''; + } + + /** + * {@inheritdoc} + * @see FilesystemInterface::pathinfo() + */ + public function pathinfo(string $path, ?int $options = null) + { + [$scheme, $path] = $this->getSchemeAndHierarchy($path); + + if ($this->normalize || ($scheme && null === $this->normalize)) { + $path = $this->normalizePathPart($path); + } + + return $this->pathinfoInternal($scheme, $path, $options); + } + + /** + * @param string|null $scheme + * @param string $path + * @param int $levels + * @return array + * @phpstan-param positive-int $levels + */ + protected function dirnameInternal(?string $scheme, string $path, int $levels = 1): array + { + $path = dirname($path, $levels); + + if (null !== $scheme && $path === '.') { + return [$scheme, '']; + } + + // In Windows dirname() may return backslashes, fix that. + if (DIRECTORY_SEPARATOR !== '/') { + $path = str_replace('\\', '/', $path); + } + + return [$scheme, $path]; + } + + /** + * @param string|null $scheme + * @param string $path + * @param int|null $options + * @return array|string + */ + protected function pathinfoInternal(?string $scheme, string $path, ?int $options = null) + { + $path = str_replace(['%2F', '%5C'], ['/', '\\'], rawurlencode($path)); + + if (null === $options) { + $info = pathinfo($path); + } else { + $info = pathinfo($path, $options); + } + + if (!is_array($info)) { + return rawurldecode($info); + } + + $info = array_map('rawurldecode', $info); + + if (null !== $scheme) { + $info['scheme'] = $scheme; + + /** @phpstan-ignore-next-line because pathinfo('') doesn't have dirname */ + $dirname = $info['dirname'] ?? '.'; + + if ('' !== $dirname && '.' !== $dirname) { + // In Windows dirname may be using backslashes, fix that. + if (DIRECTORY_SEPARATOR !== '/') { + $dirname = str_replace(DIRECTORY_SEPARATOR, '/', $dirname); + } + + $info['dirname'] = $scheme . '://' . $dirname; + } else { + $info = ['dirname' => $scheme . '://'] + $info; + } + } + + return $info; + } + + /** + * Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> array(file, tmp)). + * + * @param string $filename + * @return array + */ + protected function getSchemeAndHierarchy(string $filename): array + { + $components = explode('://', $filename, 2); + + return 2 === count($components) ? $components : [null, $components[0]]; + } + + /** + * @param string|null $scheme + * @param string $path + * @return string + */ + protected function toString(?string $scheme, string $path): string + { + if ($scheme) { + return $scheme . '://' . $path; + } + + return $path; + } + + /** + * @param string $path + * @return string + * @throws RuntimeException + */ + protected function normalizePathPart(string $path): string + { + // Quick check for empty path. + if ($path === '' || $path === '.') { + return ''; + } + + // Quick check for root. + if ($path === '/') { + return '/'; + } + + // If the last character is not '/' or any of '\', './', '//' and '..' are not found, path is clean and we're done. + if ($path[-1] !== '/' && !preg_match('`(\\\\|\./|//|\.\.)`', $path)) { + return $path; + } + + // Convert backslashes + $path = strtr($path, ['\\' => '/']); + + $parts = explode('/', $path); + + // Keep absolute paths. + $root = ''; + if ($parts[0] === '') { + $root = '/'; + array_shift($parts); + } + + $list = []; + foreach ($parts as $i => $part) { + // Remove empty parts: // and /./ + if ($part === '' || $part === '.') { + continue; + } + + // Resolve /../ by removing path part. + if ($part === '..') { + $test = array_pop($list); + if ($test === null) { + // Oops, user tried to access something outside of our root folder. + throw new RuntimeException("Bad path {$path}"); + } + } else { + $list[] = $part; + } + } + + // Build path back together. + return $root . implode('/', $list); + } +} diff --git a/system/src/Grav/Framework/Filesystem/Interfaces/FilesystemInterface.php b/system/src/Grav/Framework/Filesystem/Interfaces/FilesystemInterface.php new file mode 100644 index 0000000..d641273 --- /dev/null +++ b/system/src/Grav/Framework/Filesystem/Interfaces/FilesystemInterface.php @@ -0,0 +1,84 @@ += 1). + * @return string Returns parent path. + * @throws RuntimeException + * @phpstan-param positive-int $levels + * @api + */ + public function parent(string $path, int $levels = 1): string; + + /** + * Normalize path by cleaning up `\`, `/./`, `//` and `/../`. + * + * @param string $path A filename or path, does not need to exist as a file. + * @return string Returns normalized path. + * @throws RuntimeException + * @api + */ + public function normalize(string $path): string; + + /** + * Unicode-safe and stream-safe `\basename()` replacement. + * + * @param string $path A filename or path, does not need to exist as a file. + * @param string|null $suffix If the filename ends in suffix this will also be cut off. + * @return string + * @api + */ + public function basename(string $path, ?string $suffix = null): string; + + /** + * Unicode-safe and stream-safe `\dirname()` replacement. + * + * @see http://php.net/manual/en/function.dirname.php + * + * @param string $path A filename or path, does not need to exist as a file. + * @param int $levels The number of parent directories to go up (>= 1). + * @return string Returns path to the directory. + * @throws RuntimeException + * @phpstan-param positive-int $levels + * @api + */ + public function dirname(string $path, int $levels = 1): string; + + /** + * Unicode-safe and stream-safe `\pathinfo()` replacement. + * + * @see http://php.net/manual/en/function.pathinfo.php + * + * @param string $path A filename or path, does not need to exist as a file. + * @param int|null $options A PATHINFO_* constant. + * @return array|string + * @api + */ + public function pathinfo(string $path, ?int $options = null); +} diff --git a/system/src/Grav/Framework/Flex/Flex.php b/system/src/Grav/Framework/Flex/Flex.php new file mode 100644 index 0000000..ea931ba --- /dev/null +++ b/system/src/Grav/Framework/Flex/Flex.php @@ -0,0 +1,334 @@ + blueprint file, ...] + * @param array $config + */ + public function __construct(array $types, array $config) + { + $this->config = $config; + $this->types = []; + + foreach ($types as $type => $blueprint) { + if (!file_exists($blueprint)) { + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + $debugger->addMessage(sprintf('Flex: blueprint for flex type %s is missing', $type), 'error'); + + continue; + } + $this->addDirectoryType($type, $blueprint); + } + } + + /** + * @param string $type + * @param string $blueprint + * @param array $config + * @return $this + */ + public function addDirectoryType(string $type, string $blueprint, array $config = []) + { + $config = array_replace_recursive(['enabled' => true], $this->config, $config); + + $this->types[$type] = new FlexDirectory($type, $blueprint, $config); + + return $this; + } + + /** + * @param FlexDirectory $directory + * @return $this + */ + public function addDirectory(FlexDirectory $directory) + { + $this->types[$directory->getFlexType()] = $directory; + + return $this; + } + + /** + * @param string $type + * @return bool + */ + public function hasDirectory(string $type): bool + { + return isset($this->types[$type]); + } + + /** + * @param array|string[]|null $types + * @param bool $keepMissing + * @return array + */ + public function getDirectories(array $types = null, bool $keepMissing = false): array + { + if ($types === null) { + return $this->types; + } + + // Return the directories in the given order. + $directories = []; + foreach ($types as $type) { + $directories[$type] = $this->types[$type] ?? null; + } + + return $keepMissing ? $directories : array_filter($directories); + } + + /** + * @param string $type + * @return FlexDirectory|null + */ + public function getDirectory(string $type): ?FlexDirectory + { + return $this->types[$type] ?? null; + } + + /** + * @param string $type + * @param array|null $keys + * @param string|null $keyField + * @return FlexCollectionInterface|null + * @phpstan-return FlexCollectionInterface|null + */ + public function getCollection(string $type, array $keys = null, string $keyField = null): ?FlexCollectionInterface + { + $directory = $type ? $this->getDirectory($type) : null; + + return $directory ? $directory->getCollection($keys, $keyField) : null; + } + + /** + * @param array $keys + * @param array $options In addition to the options in getObjects(), following options can be passed: + * collection_class: Class to be used to create the collection. Defaults to ObjectCollection. + * @return FlexCollectionInterface + * @throws RuntimeException + * @phpstan-return FlexCollectionInterface + */ + public function getMixedCollection(array $keys, array $options = []): FlexCollectionInterface + { + $collectionClass = $options['collection_class'] ?? ObjectCollection::class; + if (!is_a($collectionClass, FlexCollectionInterface::class, true)) { + throw new RuntimeException(sprintf('Cannot create collection: Class %s does not exist', $collectionClass)); + } + + $objects = $this->getObjects($keys, $options); + + return new $collectionClass($objects); + } + + /** + * @param array $keys + * @param array $options Following optional options can be passed: + * types: List of allowed types. + * type: Allowed type if types isn't defined, otherwise acts as default_type. + * default_type: Set default type for objects given without type (only used if key_field isn't set). + * keep_missing: Set to true if you want to return missing objects as null. + * key_field: Key field which is used to match the objects. + * @return array + */ + public function getObjects(array $keys, array $options = []): array + { + $type = $options['type'] ?? null; + $defaultType = $options['default_type'] ?? $type ?? null; + $keyField = $options['key_field'] ?? 'flex_key'; + + // Prepare empty result lists for all requested Flex types. + $types = $options['types'] ?? (array)$type ?: null; + if ($types) { + $types = array_fill_keys($types, []); + } + $strict = isset($types); + + $guessed = []; + if ($keyField === 'flex_key') { + // We need to split Flex key lookups into individual directories. + $undefined = []; + $keyFieldFind = 'storage_key'; + + foreach ($keys as $flexKey) { + if (!$flexKey) { + continue; + } + + $flexKey = (string)$flexKey; + // Normalize key and type using fallback to default type if it was set. + [$key, $type, $guess] = $this->resolveKeyAndType($flexKey, $defaultType); + + if ($type === '' && $types) { + // Add keys which are not associated to any Flex type. They will be included to every Flex type. + foreach ($types as $type => &$array) { + $array[] = $key; + $guessed[$key][] = "{$type}.obj:{$key}"; + } + unset($array); + } elseif (!$strict || isset($types[$type])) { + // Collect keys by their Flex type. If allowed types are defined, only include values from those types. + $types[$type][] = $key; + if ($guess) { + $guessed[$key][] = "{$type}.obj:{$key}"; + } + } + } + } else { + // We are using a specific key field, make every key undefined. + $undefined = $keys; + $keyFieldFind = $keyField; + } + + if (!$types) { + return []; + } + + $list = [[]]; + foreach ($types as $type => $typeKeys) { + // Also remember to look up keys from undefined Flex types. + $lookupKeys = $undefined ? array_merge($typeKeys, $undefined) : $typeKeys; + + $collection = $this->getCollection($type, $lookupKeys, $keyFieldFind); + if ($collection && $keyFieldFind !== $keyField) { + $collection = $collection->withKeyField($keyField); + } + + $list[] = $collection ? $collection->toArray() : []; + } + + // Merge objects from individual types back together. + $list = array_merge(...$list); + + // Use the original key ordering. + if (!$guessed) { + $list = array_replace(array_fill_keys($keys, null), $list); + } else { + // We have mixed keys, we need to map flex keys back to storage keys. + $results = []; + foreach ($keys as $key) { + $flexKey = $guessed[$key] ?? $key; + if (is_array($flexKey)) { + $result = null; + foreach ($flexKey as $tryKey) { + if ($result = $list[$tryKey] ?? null) { + // Use the first matching object (conflicting objects will be ignored for now). + break; + } + } + } else { + $result = $list[$flexKey] ?? null; + } + + $results[$key] = $result; + } + + $list = $results; + } + + // Remove missing objects if not asked to keep them. + if (empty($options['keep_missing'])) { + $list = array_filter($list); + } + + return $list; + } + + /** + * @param string $key + * @param string|null $type + * @param string|null $keyField + * @return FlexObjectInterface|null + */ + public function getObject(string $key, string $type = null, string $keyField = null): ?FlexObjectInterface + { + if (null === $type && null === $keyField) { + // Special handling for quick Flex key lookups. + $keyField = 'storage_key'; + [$key, $type] = $this->resolveKeyAndType($key, $type); + } else { + $type = $this->resolveType($type); + } + + if ($type === '' || $key === '') { + return null; + } + + $directory = $this->getDirectory($type); + + return $directory ? $directory->getObject($key, $keyField) : null; + } + + /** + * @return int + */ + public function count(): int + { + return count($this->types); + } + + /** + * @param string $flexKey + * @param string|null $type + * @return array + */ + protected function resolveKeyAndType(string $flexKey, string $type = null): array + { + $guess = false; + if (strpos($flexKey, ':') !== false) { + [$type, $key] = explode(':', $flexKey, 2); + + $type = $this->resolveType($type); + } else { + $key = $flexKey; + $type = (string)$type; + $guess = true; + } + + return [$key, $type, $guess]; + } + + /** + * @param string|null $type + * @return string + */ + protected function resolveType(string $type = null): string + { + if (null !== $type && strpos($type, '.') !== false) { + return preg_replace('|\.obj$|', '', $type) ?? $type; + } + + return $type ?? ''; + } +} diff --git a/system/src/Grav/Framework/Flex/FlexCollection.php b/system/src/Grav/Framework/Flex/FlexCollection.php new file mode 100644 index 0000000..036f274 --- /dev/null +++ b/system/src/Grav/Framework/Flex/FlexCollection.php @@ -0,0 +1,733 @@ + + * @implements FlexCollectionInterface + */ +class FlexCollection extends ObjectCollection implements FlexCollectionInterface +{ + /** @var FlexDirectory */ + private $_flexDirectory; + + /** @var string */ + private $_keyField = 'storage_key'; + + /** + * Get list of cached methods. + * + * @return array Returns a list of methods with their caching information. + */ + public static function getCachedMethods(): array + { + return [ + 'getTypePrefix' => true, + 'getType' => true, + 'getFlexDirectory' => true, + 'hasFlexFeature' => true, + 'getFlexFeatures' => true, + 'getCacheKey' => true, + 'getCacheChecksum' => false, + 'getTimestamp' => true, + 'hasProperty' => true, + 'getProperty' => true, + 'hasNestedProperty' => true, + 'getNestedProperty' => true, + 'orderBy' => true, + + 'render' => false, + 'isAuthorized' => 'session', + 'search' => true, + 'sort' => true, + 'getDistinctValues' => true + ]; + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::createFromArray() + */ + public static function createFromArray(array $entries, FlexDirectory $directory, string $keyField = null) + { + $instance = new static($entries, $directory); + $instance->setKeyField($keyField); + + return $instance; + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::__construct() + */ + public function __construct(array $entries = [], FlexDirectory $directory = null) + { + // @phpstan-ignore-next-line + if (get_class($this) === __CLASS__) { + user_error('Using ' . __CLASS__ . ' directly is deprecated since Grav 1.7, use \Grav\Common\Flex\Types\Generic\GenericCollection or your own class instead', E_USER_DEPRECATED); + } + + parent::__construct($entries); + + if ($directory) { + $this->setFlexDirectory($directory)->setKey($directory->getFlexType()); + } + } + + /** + * {@inheritdoc} + * @see FlexCommonInterface::hasFlexFeature() + */ + public function hasFlexFeature(string $name): bool + { + return in_array($name, $this->getFlexFeatures(), true); + } + + /** + * {@inheritdoc} + * @see FlexCommonInterface::hasFlexFeature() + */ + public function getFlexFeatures(): array + { + /** @var array $implements */ + $implements = class_implements($this); + + $list = []; + foreach ($implements as $interface) { + if ($pos = strrpos($interface, '\\')) { + $interface = substr($interface, $pos+1); + } + + $list[] = Inflector::hyphenize(str_replace('Interface', '', $interface)); + } + + return $list; + + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::search() + */ + public function search(string $search, $properties = null, array $options = null) + { + $directory = $this->getFlexDirectory(); + $properties = $directory->getSearchProperties($properties); + $options = $directory->getSearchOptions($options); + + $matching = $this->call('search', [$search, $properties, $options]); + $matching = array_filter($matching); + + if ($matching) { + arsort($matching, SORT_NUMERIC); + } + + /** @var string[] $array */ + $array = array_keys($matching); + + /** @phpstan-var static */ + return $this->select($array); + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::sort() + */ + public function sort(array $order) + { + $criteria = Criteria::create()->orderBy($order); + + /** @phpstan-var FlexCollectionInterface $matching */ + $matching = $this->matching($criteria); + + return $matching; + } + + /** + * @param array $filters + * @return static + * @phpstan-return static + */ + public function filterBy(array $filters) + { + $expr = Criteria::expr(); + $criteria = Criteria::create(); + + foreach ($filters as $key => $value) { + $criteria->andWhere($expr->eq($key, $value)); + } + + /** @phpstan-var static */ + return $this->matching($criteria); + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::getFlexType() + */ + public function getFlexType(): string + { + return $this->_flexDirectory->getFlexType(); + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::getFlexDirectory() + */ + public function getFlexDirectory(): FlexDirectory + { + return $this->_flexDirectory; + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::getTimestamp() + */ + public function getTimestamp(): int + { + $timestamps = $this->getTimestamps(); + + return $timestamps ? max($timestamps) : time(); + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::getFlexDirectory() + */ + public function getCacheKey(): string + { + return $this->getTypePrefix() . $this->getFlexType() . '.' . sha1((string)json_encode($this->call('getKey'))); + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::getFlexDirectory() + */ + public function getCacheChecksum(): string + { + $list = []; + /** + * @var string $key + * @var FlexObjectInterface $object + */ + foreach ($this as $key => $object) { + $list[$key] = $object->getCacheChecksum(); + } + + return sha1((string)json_encode($list)); + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::getFlexDirectory() + */ + public function getTimestamps(): array + { + /** @var int[] $timestamps */ + $timestamps = $this->call('getTimestamp'); + + return $timestamps; + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::getFlexDirectory() + */ + public function getStorageKeys(): array + { + /** @var string[] $keys */ + $keys = $this->call('getStorageKey'); + + return $keys; + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::getFlexDirectory() + */ + public function getFlexKeys(): array + { + /** @var string[] $keys */ + $keys = $this->call('getFlexKey'); + + return $keys; + } + + /** + * Get all the values in property. + * + * Supports either single scalar values or array of scalar values. + * + * @param string $property Object property to be used to make groups. + * @param string|null $separator Separator, defaults to '.' + * @return array + */ + public function getDistinctValues(string $property, string $separator = null): array + { + $list = []; + + /** @var FlexObjectInterface $element */ + foreach ($this->getIterator() as $element) { + $value = (array)$element->getNestedProperty($property, null, $separator); + foreach ($value as $v) { + if (is_scalar($v)) { + $t = gettype($v) . (string)$v; + $list[$t] = $v; + } + } + } + + return array_values($list); + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::withKeyField() + */ + public function withKeyField(string $keyField = null) + { + $keyField = $keyField ?: 'key'; + if ($keyField === $this->getKeyField()) { + return $this; + } + + $entries = []; + foreach ($this as $key => $object) { + // TODO: remove hardcoded logic + if ($keyField === 'storage_key') { + $entries[$object->getStorageKey()] = $object; + } elseif ($keyField === 'flex_key') { + $entries[$object->getFlexKey()] = $object; + } elseif ($keyField === 'key') { + $entries[$object->getKey()] = $object; + } + } + + return $this->createFrom($entries, $keyField); + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::getIndex() + */ + public function getIndex() + { + /** @phpstan-var FlexIndexInterface */ + return $this->getFlexDirectory()->getIndex($this->getKeys(), $this->getKeyField()); + } + + /** + * @inheritdoc} + * @see FlexCollectionInterface::getCollection() + * @return $this + */ + public function getCollection() + { + return $this; + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::render() + */ + public function render(string $layout = null, array $context = []) + { + if (!$layout) { + $config = $this->getTemplateConfig(); + $layout = $config['collection']['defaults']['layout'] ?? 'default'; + } + + $type = $this->getFlexType(); + + $grav = Grav::instance(); + + /** @var Debugger $debugger */ + $debugger = $grav['debugger']; + $debugger->startTimer('flex-collection-' . ($debugKey = uniqid($type, false)), 'Render Collection ' . $type . ' (' . $layout . ')'); + + $key = null; + foreach ($context as $value) { + if (!is_scalar($value)) { + $key = false; + break; + } + } + + if ($key !== false) { + $key = md5($this->getCacheKey() . '.' . $layout . json_encode($context)); + $cache = $this->getCache('render'); + } else { + $cache = null; + } + + try { + $data = $cache && $key ? $cache->get($key) : null; + + $block = $data ? HtmlBlock::fromArray($data) : null; + } catch (InvalidArgumentException $e) { + $debugger->addException($e); + $block = null; + } catch (\InvalidArgumentException $e) { + $debugger->addException($e); + $block = null; + } + + $checksum = $this->getCacheChecksum(); + if ($block && $checksum !== $block->getChecksum()) { + $block = null; + } + + if (!$block) { + $block = HtmlBlock::create($key ?: null); + $block->setChecksum($checksum); + if (!$key) { + $block->disableCache(); + } + + $event = new Event([ + 'type' => 'flex', + 'directory' => $this->getFlexDirectory(), + 'collection' => $this, + 'layout' => &$layout, + 'context' => &$context + ]); + $this->triggerEvent('onRender', $event); + + $output = $this->getTemplate($layout)->render( + [ + 'grav' => $grav, + 'config' => $grav['config'], + 'block' => $block, + 'directory' => $this->getFlexDirectory(), + 'collection' => $this, + 'layout' => $layout + ] + $context + ); + + if ($debugger->enabled() && + !($grav['uri']->getContentType() === 'application/json' || $grav['uri']->extension() === 'json')) { + $output = "\n\n{$output}\n\n"; + } + + $block->setContent($output); + + try { + $cache && $key && $block->isCached() && $cache->set($key, $block->toArray()); + } catch (InvalidArgumentException $e) { + $debugger->addException($e); + } + } + + $debugger->stopTimer('flex-collection-' . $debugKey); + + return $block; + } + + /** + * @param FlexDirectory $type + * @return $this + */ + public function setFlexDirectory(FlexDirectory $type) + { + $this->_flexDirectory = $type; + + return $this; + } + + /** + * @param string $key + * @return array + */ + public function getMetaData($key): array + { + $object = $this->get($key); + + return $object instanceof FlexObjectInterface ? $object->getMetaData() : []; + } + + /** + * @param string|null $namespace + * @return CacheInterface + */ + public function getCache(string $namespace = null) + { + return $this->_flexDirectory->getCache($namespace); + } + + /** + * @return string + */ + public function getKeyField(): string + { + return $this->_keyField; + } + + /** + * @param string $action + * @param string|null $scope + * @param UserInterface|null $user + * @return static + * @phpstan-return static + */ + public function isAuthorized(string $action, string $scope = null, UserInterface $user = null) + { + $list = $this->call('isAuthorized', [$action, $scope, $user]); + $list = array_filter($list); + + /** @var string[] $keys */ + $keys = array_keys($list); + + /** @phpstan-var static */ + return $this->select($keys); + } + + /** + * @param string $value + * @param string $field + * @return FlexObjectInterface|null + * @phpstan-return T|null + */ + public function find($value, $field = 'id') + { + if ($value) { + foreach ($this as $element) { + if (mb_strtolower($element->getProperty($field)) === mb_strtolower($value)) { + return $element; + } + } + } + + return null; + } + + /** + * @return array + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + $elements = []; + + /** + * @var string $key + * @var array|FlexObject $object + */ + foreach ($this->getElements() as $key => $object) { + $elements[$key] = is_array($object) ? $object : $object->jsonSerialize(); + } + + return $elements; + } + + /** + * @return array + */ + #[\ReturnTypeWillChange] + public function __debugInfo() + { + return [ + 'type:private' => $this->getFlexType(), + 'key:private' => $this->getKey(), + 'objects_key:private' => $this->getKeyField(), + 'objects:private' => $this->getElements() + ]; + } + + /** + * Creates a new instance from the specified elements. + * + * This method is provided for derived classes to specify how a new + * instance should be created when constructor semantics have changed. + * + * @param array $elements Elements. + * @param string|null $keyField + * @return static + * @phpstan-return static + * @throws \InvalidArgumentException + */ + protected function createFrom(array $elements, $keyField = null) + { + $collection = new static($elements, $this->_flexDirectory); + $collection->setKeyField($keyField ?: $this->_keyField); + + return $collection; + } + + /** + * @return string + */ + protected function getTypePrefix(): string + { + return 'c.'; + } + + /** + * @return array + */ + protected function getTemplateConfig(): array + { + $config = $this->getFlexDirectory()->getConfig('site.templates', []); + $defaults = array_replace($config['defaults'] ?? [], $config['collection']['defaults'] ?? []); + $config['collection']['defaults'] = $defaults; + + return $config; + } + + /** + * @param string $layout + * @return array + */ + protected function getTemplatePaths(string $layout): array + { + $config = $this->getTemplateConfig(); + $type = $this->getFlexType(); + $defaults = $config['collection']['defaults'] ?? []; + + $ext = $defaults['ext'] ?? '.html.twig'; + $types = array_unique(array_merge([$type], (array)($defaults['type'] ?? null))); + $paths = $config['collection']['paths'] ?? [ + 'flex/{TYPE}/collection/{LAYOUT}{EXT}', + 'flex-objects/layouts/{TYPE}/collection/{LAYOUT}{EXT}' + ]; + $table = ['TYPE' => '%1$s', 'LAYOUT' => '%2$s', 'EXT' => '%3$s']; + + $lookups = []; + foreach ($paths as $path) { + $path = Utils::simpleTemplate($path, $table); + foreach ($types as $type) { + $lookups[] = sprintf($path, $type, $layout, $ext); + } + } + + return array_unique($lookups); + } + + /** + * @param string $layout + * @return Template|TemplateWrapper + * @throws LoaderError + * @throws SyntaxError + */ + protected function getTemplate($layout) + { + $grav = Grav::instance(); + + /** @var Twig $twig */ + $twig = $grav['twig']; + + try { + return $twig->twig()->resolveTemplate($this->getTemplatePaths($layout)); + } catch (LoaderError $e) { + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + $debugger->addException($e); + + return $twig->twig()->resolveTemplate(['flex/404.html.twig']); + } + } + + /** + * @param string $type + * @return FlexDirectory + */ + protected function getRelatedDirectory($type): ?FlexDirectory + { + /** @var Flex $flex */ + $flex = Grav::instance()['flex']; + + return $flex->getDirectory($type); + } + + /** + * @param string|null $keyField + * @return void + */ + protected function setKeyField($keyField = null): void + { + $this->_keyField = $keyField ?? 'storage_key'; + } + + // DEPRECATED METHODS + + /** + * @param bool $prefix + * @return string + * @deprecated 1.6 Use `->getFlexType()` instead. + */ + public function getType($prefix = false) + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->getFlexType() method instead', E_USER_DEPRECATED); + + $type = $prefix ? $this->getTypePrefix() : ''; + + return $type . $this->getFlexType(); + } + + /** + * @param string $name + * @param object|null $event + * @return $this + * @deprecated 1.7, moved to \Grav\Common\Flex\Traits\FlexObjectTrait + */ + public function triggerEvent(string $name, $event = null) + { + user_error(__METHOD__ . '() is deprecated since Grav 1.7, moved to \Grav\Common\Flex\Traits\FlexObjectTrait', E_USER_DEPRECATED); + + if (null === $event) { + $event = new Event([ + 'type' => 'flex', + 'directory' => $this->getFlexDirectory(), + 'collection' => $this + ]); + } + if (strpos($name, 'onFlexCollection') !== 0 && strpos($name, 'on') === 0) { + $name = 'onFlexCollection' . substr($name, 2); + } + + $grav = Grav::instance(); + if ($event instanceof Event) { + $grav->fireEvent($name, $event); + } else { + $grav->dispatchEvent($event); + } + + + return $this; + } +} diff --git a/system/src/Grav/Framework/Flex/FlexDirectory.php b/system/src/Grav/Framework/Flex/FlexDirectory.php new file mode 100644 index 0000000..fdf2a4b --- /dev/null +++ b/system/src/Grav/Framework/Flex/FlexDirectory.php @@ -0,0 +1,1187 @@ +[] + */ + protected $indexes = []; + /** + * @var FlexCollectionInterface|null + * @phpstan-var FlexCollectionInterface|null + */ + protected $collection; + /** @var bool */ + protected $enabled; + /** @var array */ + protected $defaults; + /** @var Config */ + protected $config; + /** @var FlexStorageInterface */ + protected $storage; + /** @var CacheInterface[] */ + protected $cache; + /** @var FlexObjectInterface[] */ + protected $objects; + /** @var string */ + protected $objectClassName; + /** @var string */ + protected $collectionClassName; + /** @var string */ + protected $indexClassName; + + /** @var string|null */ + private $_authorize; + + /** + * FlexDirectory constructor. + * @param string $type + * @param string $blueprint_file + * @param array $defaults + */ + public function __construct(string $type, string $blueprint_file, array $defaults = []) + { + $this->type = $type; + $this->blueprints = []; + $this->blueprint_file = $blueprint_file; + $this->defaults = $defaults; + $this->enabled = !empty($defaults['enabled']); + $this->objects = []; + } + + /** + * @return bool + */ + public function isListed(): bool + { + $grav = Grav::instance(); + + /** @var Flex $flex */ + $flex = $grav['flex']; + $directory = $flex->getDirectory($this->type); + + return null !== $directory; + } + + /** + * @return bool + */ + public function isEnabled(): bool + { + return $this->enabled; + } + + /** + * @return string + */ + public function getFlexType(): string + { + return $this->type; + } + + /** + * @return string + */ + public function getTitle(): string + { + return $this->getBlueprintInternal()->get('title', ucfirst($this->getFlexType())); + } + + /** + * @return string + */ + public function getDescription(): string + { + return $this->getBlueprintInternal()->get('description', ''); + } + + /** + * @param string|null $name + * @param mixed $default + * @return mixed + */ + public function getConfig(string $name = null, $default = null) + { + if (null === $this->config) { + $config = $this->getBlueprintInternal()->get('config', []); + $config = is_array($config) ? array_replace_recursive($config, $this->defaults, $this->getDirectoryConfig($config['admin']['views']['configure']['form'] ?? $config['admin']['configure']['form'] ?? null)) : null; + if (!is_array($config)) { + throw new RuntimeException('Bad configuration'); + } + + $this->config = new Config($config); + } + + return null === $name ? $this->config : $this->config->get($name, $default); + } + + /** + * @param string|string[]|null $properties + * @return array + */ + public function getSearchProperties($properties = null): array + { + if (null !== $properties) { + return (array)$properties; + } + + $properties = $this->getConfig('data.search.fields'); + if (!$properties) { + $fields = $this->getConfig('admin.views.list.fields') ?? $this->getConfig('admin.list.fields', []); + foreach ($fields as $property => $value) { + if (!empty($value['link'])) { + $properties[] = $property; + } + } + } + + return $properties; + } + + /** + * @param array|null $options + * @return array + */ + public function getSearchOptions(array $options = null): array + { + if (empty($options['merge'])) { + return $options ?? (array)$this->getConfig('data.search.options'); + } + + unset($options['merge']); + + return $options + (array)$this->getConfig('data.search.options'); + } + + /** + * @param string|null $name + * @param array $options + * @return FlexFormInterface + * @internal + */ + public function getDirectoryForm(string $name = null, array $options = []) + { + $name = $name ?: $this->getConfig('admin.views.configure.form', '') ?: $this->getConfig('admin.configure.form', ''); + + return new FlexDirectoryForm($name ?? '', $this, $options); + } + + /** + * @return Blueprint + * @internal + */ + public function getDirectoryBlueprint() + { + $name = 'configure'; + + $type = $this->getBlueprint(); + $overrides = $type->get("blueprints/{$name}"); + + $path = "blueprints://flex/shared/{$name}.yaml"; + $blueprint = new Blueprint($path); + $blueprint->load(); + if (isset($overrides['fields'])) { + $blueprint->embed('form/fields/tabs/fields', $overrides['fields']); + } + $blueprint->init(); + + return $blueprint; + } + + /** + * @param string $name + * @param array $data + * @return void + * @throws Exception + * @internal + */ + public function saveDirectoryConfig(string $name, array $data) + { + $grav = Grav::instance(); + + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + + $filename = $this->getDirectoryConfigUri($name); + if (file_exists($filename)) { + $filename = $locator->findResource($filename, true); + } else { + $filesystem = Filesystem::getInstance(); + $dirname = $filesystem->dirname($filename); + $basename = $filesystem->basename($filename); + $dirname = $locator->findResource($dirname, true) ?: $locator->findResource($dirname, true, true); + $filename = "{$dirname}/{$basename}"; + } + + $file = YamlFile::instance($filename); + if (!empty($data)) { + $file->save($data); + } else { + $file->delete(); + } + } + + /** + * @param string $name + * @return array + * @internal + */ + public function loadDirectoryConfig(string $name): array + { + $grav = Grav::instance(); + + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + $uri = $this->getDirectoryConfigUri($name); + + // If configuration is found in main configuration, use it. + if (str_starts_with($uri, 'config://')) { + $path = str_replace('/', '.', substr($uri, 9, -5)); + + return (array)$grav['config']->get($path); + } + + // Load the configuration file. + $filename = $locator->findResource($uri, true); + if ($filename === false) { + return []; + } + + $file = YamlFile::instance($filename); + + return $file->content(); + } + + /** + * @param string|null $name + * @return string + */ + public function getDirectoryConfigUri(string $name = null): string + { + $name = $name ?: $this->getFlexType(); + $blueprint = $this->getBlueprint(); + + return $blueprint->get('blueprints/views/configure/file') ?? $blueprint->get('blueprints/configure/file') ?? "config://flex/{$name}.yaml"; + } + + /** + * @param string|null $name + * @return array + */ + protected function getDirectoryConfig(string $name = null): array + { + $grav = Grav::instance(); + + /** @var Config $config */ + $config = $grav['config']; + $name = $name ?: $this->getFlexType(); + + return $config->get("flex.{$name}", []); + } + + /** + * Returns a new uninitialized instance of blueprint. + * + * Always use $object->getBlueprint() or $object->getForm()->getBlueprint() instead. + * + * @param string $type + * @param string $context + * @return Blueprint + */ + public function getBlueprint(string $type = '', string $context = '') + { + return clone $this->getBlueprintInternal($type, $context); + } + + /** + * @param string $view + * @return string + */ + public function getBlueprintFile(string $view = ''): string + { + $file = $this->blueprint_file; + if ($view !== '') { + $file = preg_replace('/\.yaml/', "/{$view}.yaml", $file); + } + + return (string)$file; + } + + /** + * Get collection. In the site this will be filtered by the default filters (published etc). + * + * Use $directory->getIndex() if you want unfiltered collection. + * + * @param array|null $keys Array of keys. + * @param string|null $keyField Field to be used as the key. + * @return FlexCollectionInterface + * @phpstan-return FlexCollectionInterface + */ + public function getCollection(array $keys = null, string $keyField = null): FlexCollectionInterface + { + // Get all selected entries. + $index = $this->getIndex($keys, $keyField); + + if (!Utils::isAdminPlugin()) { + // If not in admin, filter the list by using default filters. + $filters = (array)$this->getConfig('site.filter', []); + + foreach ($filters as $filter) { + $index = $index->{$filter}(); + } + } + + return $index; + } + + /** + * Get the full collection of all stored objects. + * + * Use $directory->getCollection() if you want a filtered collection. + * + * @param array|null $keys Array of keys. + * @param string|null $keyField Field to be used as the key. + * @return FlexIndexInterface + * @phpstan-return FlexIndexInterface + */ + public function getIndex(array $keys = null, string $keyField = null): FlexIndexInterface + { + $keyField = $keyField ?? ''; + $index = $this->indexes[$keyField] ?? $this->loadIndex($keyField); + $index = clone $index; + + if (null !== $keys) { + /** @var FlexIndexInterface $index */ + $index = $index->select($keys); + } + + return $index->getIndex(); + } + + /** + * Returns an object if it exists. If no arguments are passed (or both of them are null), method creates a new empty object. + * + * Note: It is not safe to use the object without checking if the user can access it. + * + * @param string|null $key + * @param string|null $keyField Field to be used as the key. + * @return FlexObjectInterface|null + */ + public function getObject($key = null, string $keyField = null): ?FlexObjectInterface + { + if (null === $key) { + return $this->createObject([], ''); + } + + $keyField = $keyField ?? ''; + $index = $this->indexes[$keyField] ?? $this->loadIndex($keyField); + + return $index->get($key); + } + + /** + * @param string|null $namespace + * @return CacheInterface + */ + public function getCache(string $namespace = null) + { + $namespace = $namespace ?: 'index'; + $cache = $this->cache[$namespace] ?? null; + + if (null === $cache) { + try { + $grav = Grav::instance(); + + /** @var Cache $gravCache */ + $gravCache = $grav['cache']; + $config = $this->getConfig('object.cache.' . $namespace); + if (empty($config['enabled'])) { + $cache = new MemoryCache('flex-objects-' . $this->getFlexType()); + } else { + $lifetime = $config['lifetime'] ?? 60; + + $key = $gravCache->getKey(); + if (Utils::isAdminPlugin()) { + $key = substr($key, 0, -1); + } + $cache = new DoctrineCache($gravCache->getCacheDriver(), 'flex-objects-' . $this->getFlexType() . $key, $lifetime); + } + } catch (Exception $e) { + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + $debugger->addException($e); + + $cache = new MemoryCache('flex-objects-' . $this->getFlexType()); + } + + // Disable cache key validation. + $cache->setValidation(false); + $this->cache[$namespace] = $cache; + } + + return $cache; + } + + /** + * @return $this + */ + public function clearCache() + { + $grav = Grav::instance(); + + /** @var Debugger $debugger */ + $debugger = $grav['debugger']; + $debugger->addMessage(sprintf('Flex: Clearing all %s cache', $this->type), 'debug'); + + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + $locator->clearCache(); + + $this->getCache('index')->clear(); + $this->getCache('object')->clear(); + $this->getCache('render')->clear(); + + $this->indexes = []; + $this->objects = []; + + return $this; + } + + /** + * @param string|null $key + * @return string|null + */ + public function getStorageFolder(string $key = null): ?string + { + return $this->getStorage()->getStoragePath($key); + } + + /** + * @param string|null $key + * @return string|null + */ + public function getMediaFolder(string $key = null): ?string + { + return $this->getStorage()->getMediaPath($key); + } + + /** + * @return FlexStorageInterface + */ + public function getStorage(): FlexStorageInterface + { + if (null === $this->storage) { + $this->storage = $this->createStorage(); + } + + return $this->storage; + } + + /** + * @param array $data + * @param string $key + * @param bool $validate + * @return FlexObjectInterface + */ + public function createObject(array $data, string $key = '', bool $validate = false): FlexObjectInterface + { + /** @phpstan-var class-string $className */ + $className = $this->objectClassName ?: $this->getObjectClass(); + if (!is_a($className, FlexObjectInterface::class, true)) { + throw new \RuntimeException('Bad object class: ' . $className); + } + + return new $className($data, $key, $this, $validate); + } + + /** + * @param array $entries + * @param string|null $keyField + * @return FlexCollectionInterface + * @phpstan-return FlexCollectionInterface + */ + public function createCollection(array $entries, string $keyField = null): FlexCollectionInterface + { + /** phpstan-var class-string $className */ + $className = $this->collectionClassName ?: $this->getCollectionClass(); + if (!is_a($className, FlexCollectionInterface::class, true)) { + throw new \RuntimeException('Bad collection class: ' . $className); + } + + return $className::createFromArray($entries, $this, $keyField); + } + + /** + * @param array $entries + * @param string|null $keyField + * @return FlexIndexInterface + * @phpstan-return FlexIndexInterface + */ + public function createIndex(array $entries, string $keyField = null): FlexIndexInterface + { + /** @phpstan-var class-string $className */ + $className = $this->indexClassName ?: $this->getIndexClass(); + if (!is_a($className, FlexIndexInterface::class, true)) { + throw new \RuntimeException('Bad index class: ' . $className); + } + + return $className::createFromArray($entries, $this, $keyField); + } + + /** + * @return string + */ + public function getObjectClass(): string + { + if (!$this->objectClassName) { + $this->objectClassName = $this->getConfig('data.object', GenericObject::class); + } + + return $this->objectClassName; + } + + /** + * @return string + */ + public function getCollectionClass(): string + { + if (!$this->collectionClassName) { + $this->collectionClassName = $this->getConfig('data.collection', GenericCollection::class); + } + + return $this->collectionClassName; + } + + + /** + * @return string + */ + public function getIndexClass(): string + { + if (!$this->indexClassName) { + $this->indexClassName = $this->getConfig('data.index', GenericIndex::class); + } + + return $this->indexClassName; + } + + /** + * @param array $entries + * @param string|null $keyField + * @return FlexCollectionInterface + * @phpstan-return FlexCollectionInterface + */ + public function loadCollection(array $entries, string $keyField = null): FlexCollectionInterface + { + return $this->createCollection($this->loadObjects($entries), $keyField); + } + + /** + * @param array $entries + * @return FlexObjectInterface[] + * @internal + */ + public function loadObjects(array $entries): array + { + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + + $keys = []; + $rows = []; + $fetch = []; + + // Build lookup arrays with storage keys for the objects. + foreach ($entries as $key => $value) { + $k = $value['storage_key'] ?? ''; + if ($k === '') { + continue; + } + $v = $this->objects[$k] ?? null; + $keys[$k] = $key; + $rows[$k] = $v; + if (!$v) { + $fetch[] = $k; + } + } + + // Attempt to fetch missing rows from the cache. + if ($fetch) { + $rows = (array)array_replace($rows, $this->loadCachedObjects($fetch)); + } + + // Read missing rows from the storage. + $updated = []; + $storage = $this->getStorage(); + $rows = $storage->readRows($rows, $updated); + + // Create objects from the rows. + $isListed = $this->isListed(); + $list = []; + foreach ($rows as $storageKey => $row) { + $usedKey = $keys[$storageKey]; + + if ($row instanceof FlexObjectInterface) { + $object = $row; + } else { + if ($row === null) { + $debugger->addMessage(sprintf('Flex: Object %s was not found from %s storage', $storageKey, $this->type), 'debug'); + continue; + } + + if (isset($row['__ERROR'])) { + $message = sprintf('Flex: Object %s is broken in %s storage: %s', $storageKey, $this->type, $row['__ERROR']); + $debugger->addException(new RuntimeException($message)); + $debugger->addMessage($message, 'error'); + continue; + } + + if (!isset($row['__META'])) { + $row['__META'] = [ + 'storage_key' => $storageKey, + 'storage_timestamp' => $entries[$usedKey]['storage_timestamp'] ?? 0, + ]; + } + + $key = $row['__META']['key'] ?? $entries[$usedKey]['key'] ?? $usedKey; + $object = $this->createObject($row, $key, false); + $this->objects[$storageKey] = $object; + if ($isListed) { + // If unserialize works for the object, serialize the object to speed up the loading. + $updated[$storageKey] = $object; + } + } + + $list[$usedKey] = $object; + } + + // Store updated rows to the cache. + if ($updated) { + $cache = $this->getCache('object'); + if (!$cache instanceof MemoryCache) { + ///** @var Debugger $debugger */ + //$debugger = Grav::instance()['debugger']; + //$debugger->addMessage(sprintf('Flex: Caching %d %s', \count($entries), $this->type), 'debug'); + } + try { + $cache->setMultiple($updated); + } catch (InvalidArgumentException $e) { + $debugger->addException($e); + // TODO: log about the issue. + } + } + + if ($fetch) { + $debugger->stopTimer('flex-objects'); + } + + return $list; + } + + protected function loadCachedObjects(array $fetch): array + { + if (!$fetch) { + return []; + } + + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + + $cache = $this->getCache('object'); + + // Attempt to fetch missing rows from the cache. + $fetched = []; + try { + $loading = count($fetch); + + $debugger->startTimer('flex-objects', sprintf('Flex: Loading %d %s', $loading, $this->type)); + + $fetched = (array)$cache->getMultiple($fetch); + if ($fetched) { + $index = $this->loadIndex('storage_key'); + + // Make sure cached objects are up to date: compare against index checksum/timestamp. + /** + * @var string $key + * @var mixed $value + */ + foreach ($fetched as $key => $value) { + if ($value instanceof FlexObjectInterface) { + $objectMeta = $value->getMetaData(); + } else { + $objectMeta = $value['__META'] ?? []; + } + $indexMeta = $index->getMetaData($key); + + $indexChecksum = $indexMeta['checksum'] ?? $indexMeta['storage_timestamp'] ?? null; + $objectChecksum = $objectMeta['checksum'] ?? $objectMeta['storage_timestamp'] ?? null; + if ($indexChecksum !== $objectChecksum) { + unset($fetched[$key]); + } + } + } + + } catch (InvalidArgumentException $e) { + $debugger->addException($e); + } + + return $fetched; + } + + /** + * @return void + */ + public function reloadIndex(): void + { + $this->getCache('index')->clear(); + $this->getIndex()::loadEntriesFromStorage($this->getStorage()); + + $this->indexes = []; + $this->objects = []; + } + + /** + * @param string $scope + * @param string $action + * @return string + */ + public function getAuthorizeRule(string $scope, string $action): string + { + if (!$this->_authorize) { + $config = $this->getConfig('admin.permissions'); + if ($config) { + $this->_authorize = array_key_first($config) . '.%2$s'; + } else { + $this->_authorize = '%1$s.flex-object.%2$s'; + } + } + + return sprintf($this->_authorize, $scope, $action); + } + + /** + * @param string $type_view + * @param string $context + * @return Blueprint + */ + protected function getBlueprintInternal(string $type_view = '', string $context = '') + { + if (!isset($this->blueprints[$type_view])) { + if (!file_exists($this->blueprint_file)) { + throw new RuntimeException(sprintf('Flex: Blueprint file for %s is missing', $this->type)); + } + + $parts = explode('.', rtrim($type_view, '.'), 2); + $type = array_shift($parts); + $view = array_shift($parts) ?: ''; + + $blueprint = new Blueprint($this->getBlueprintFile($view)); + $blueprint->addDynamicHandler('data', function (array &$field, $property, array &$call) { + $this->dynamicDataField($field, $property, $call); + }); + $blueprint->addDynamicHandler('flex', function (array &$field, $property, array &$call) { + $this->dynamicFlexField($field, $property, $call); + }); + $blueprint->addDynamicHandler('authorize', function (array &$field, $property, array &$call) { + $this->dynamicAuthorizeField($field, $property, $call); + }); + + if ($context) { + $blueprint->setContext($context); + } + + $blueprint->load($type ?: null); + if ($blueprint->get('type') === 'flex-objects' && isset(Grav::instance()['admin'])) { + $blueprintBase = (new Blueprint('plugin://flex-objects/blueprints/flex-objects.yaml'))->load(); + $blueprint->extend($blueprintBase, true); + } + + $this->blueprints[$type_view] = $blueprint; + } + + return $this->blueprints[$type_view]; + } + + /** + * @param array $field + * @param string $property + * @param array $call + * @return void + */ + protected function dynamicDataField(array &$field, $property, array $call) + { + $params = $call['params']; + if (is_array($params)) { + $function = array_shift($params); + } else { + $function = $params; + $params = []; + } + + $object = $call['object']; + if ($function === '\Grav\Common\Page\Pages::pageTypes') { + $params = [$object instanceof PageInterface && $object->isModule() ? 'modular' : 'standard']; + } + + $data = null; + if (is_callable($function)) { + $data = call_user_func_array($function, $params); + } + + // If function returns a value, + if (null !== $data) { + if (is_array($data) && isset($field[$property]) && is_array($field[$property])) { + // Combine field and @data-field together. + $field[$property] += $data; + } else { + // Or create/replace field with @data-field. + $field[$property] = $data; + } + } + } + + /** + * @param array $field + * @param string $property + * @param array $call + * @return void + */ + protected function dynamicFlexField(array &$field, $property, array $call): void + { + $params = (array)$call['params']; + $object = $call['object'] ?? null; + $method = array_shift($params); + $not = false; + if (str_starts_with($method, '!')) { + $method = substr($method, 1); + $not = true; + } elseif (str_starts_with($method, 'not ')) { + $method = substr($method, 4); + $not = true; + } + $method = trim($method); + + if ($object && method_exists($object, $method)) { + $value = $object->{$method}(...$params); + if (is_array($value) && isset($field[$property]) && is_array($field[$property])) { + $value = $this->mergeArrays($field[$property], $value); + } + $value = $not ? !$value : $value; + + if ($property === 'ignore' && $value) { + Blueprint::addPropertyRecursive($field, 'validate', ['ignore' => true]); + } else { + $field[$property] = $value; + } + } + } + + /** + * @param array $field + * @param string $property + * @param array $call + * @return void + */ + protected function dynamicAuthorizeField(array &$field, $property, array $call): void + { + $params = (array)$call['params']; + $object = $call['object'] ?? null; + $permission = array_shift($params); + $not = false; + if (str_starts_with($permission, '!')) { + $permission = substr($permission, 1); + $not = true; + } elseif (str_starts_with($permission, 'not ')) { + $permission = substr($permission, 4); + $not = true; + } + $permission = trim($permission); + + if ($object) { + $value = $object->isAuthorized($permission) ?? false; + + $field[$property] = $not ? !$value : $value; + } + } + + /** + * @param array $array1 + * @param array $array2 + * @return array + */ + protected function mergeArrays(array $array1, array $array2): array + { + foreach ($array2 as $key => $value) { + if (is_array($value) && isset($array1[$key]) && is_array($array1[$key])) { + $array1[$key] = $this->mergeArrays($array1[$key], $value); + } else { + $array1[$key] = $value; + } + } + + return $array1; + } + + /** + * @return FlexStorageInterface + */ + protected function createStorage(): FlexStorageInterface + { + $this->collection = $this->createCollection([]); + + $storage = $this->getConfig('data.storage'); + + if (!is_array($storage)) { + $storage = ['options' => ['folder' => $storage]]; + } + + $className = $storage['class'] ?? SimpleStorage::class; + $options = $storage['options'] ?? []; + + if (!is_a($className, FlexStorageInterface::class, true)) { + throw new \RuntimeException('Bad storage class: ' . $className); + } + + return new $className($options); + } + + /** + * @param string $keyField + * @return FlexIndexInterface + * @phpstan-return FlexIndexInterface + */ + protected function loadIndex(string $keyField): FlexIndexInterface + { + static $i = 0; + + $index = $this->indexes[$keyField] ?? null; + if (null !== $index) { + return $index; + } + + $index = $this->indexes['storage_key'] ?? null; + if (null === $index) { + $i++; + $j = $i; + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + $debugger->startTimer('flex-keys-' . $this->type . $j, "Flex: Loading {$this->type} index"); + + $storage = $this->getStorage(); + $cache = $this->getCache('index'); + + try { + $keys = $cache->get('__keys'); + } catch (InvalidArgumentException $e) { + $debugger->addException($e); + $keys = null; + } + + if (!is_array($keys)) { + /** @phpstan-var class-string $className */ + $className = $this->getIndexClass(); + $keys = $className::loadEntriesFromStorage($storage); + if (!$cache instanceof MemoryCache) { + $debugger->addMessage( + sprintf('Flex: Caching %s index of %d objects', $this->type, count($keys)), + 'debug' + ); + } + try { + $cache->set('__keys', $keys); + } catch (InvalidArgumentException $e) { + $debugger->addException($e); + // TODO: log about the issue. + } + } + + $ordering = $this->getConfig('data.ordering', []); + + // We need to do this in two steps as orderBy() calls loadIndex() again and we do not want infinite loop. + $this->indexes['storage_key'] = $index = $this->createIndex($keys, 'storage_key'); + if ($ordering) { + /** @var FlexCollectionInterface $collection */ + $collection = $this->indexes['storage_key']->orderBy($ordering); + $this->indexes['storage_key'] = $index = $collection->getIndex(); + } + + $debugger->stopTimer('flex-keys-' . $this->type . $j); + } + + if ($keyField !== 'storage_key') { + $this->indexes[$keyField] = $index = $index->withKeyField($keyField ?: null); + } + + return $index; + } + + /** + * @param string $action + * @return string + */ + protected function getAuthorizeAction(string $action): string + { + // Handle special action save, which can mean either update or create. + if ($action === 'save') { + $action = 'create'; + } + + return $action; + } + /** + * @return UserInterface|null + */ + protected function getActiveUser(): ?UserInterface + { + /** @var UserInterface|null $user */ + $user = Grav::instance()['user'] ?? null; + + return $user; + } + + /** + * @return string + */ + protected function getAuthorizeScope(): string + { + return isset(Grav::instance()['admin']) ? 'admin' : 'site'; + } + + // DEPRECATED METHODS + + /** + * @return string + * @deprecated 1.6 Use ->getFlexType() method instead. + */ + public function getType(): string + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->getFlexType() method instead', E_USER_DEPRECATED); + + return $this->type; + } + + /** + * @param array $data + * @param string|null $key + * @return FlexObjectInterface + * @deprecated 1.7 Use $object->update()->save() instead. + */ + public function update(array $data, string $key = null): FlexObjectInterface + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() should not be used anymore: use $object->update()->save() instead.', E_USER_DEPRECATED); + + $object = null !== $key ? $this->getIndex()->get($key): null; + + $storage = $this->getStorage(); + + if (null === $object) { + $object = $this->createObject($data, $key ?? '', true); + $key = $object->getStorageKey(); + + if ($key) { + $storage->replaceRows([$key => $object->prepareStorage()]); + } else { + $storage->createRows([$object->prepareStorage()]); + } + } else { + $oldKey = $object->getStorageKey(); + $object->update($data); + $newKey = $object->getStorageKey(); + + if ($oldKey !== $newKey) { + if (method_exists($object, 'triggerEvent')) { + $object->triggerEvent('move'); + } + $storage->renameRow($oldKey, $newKey); + // TODO: media support. + } + + $object->save(); + } + + try { + $this->clearCache(); + } catch (InvalidArgumentException $e) { + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + $debugger->addException($e); + + // Caching failed, but we can ignore that for now. + } + + return $object; + } + + /** + * @param string $key + * @return FlexObjectInterface|null + * @deprecated 1.7 Use $object->delete() instead. + */ + public function remove(string $key): ?FlexObjectInterface + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() should not be used anymore: use $object->delete() instead.', E_USER_DEPRECATED); + + $object = $this->getIndex()->get($key); + if (!$object) { + return null; + } + + $object->delete(); + + return $object; + } +} diff --git a/system/src/Grav/Framework/Flex/FlexDirectoryForm.php b/system/src/Grav/Framework/Flex/FlexDirectoryForm.php new file mode 100644 index 0000000..ec18909 --- /dev/null +++ b/system/src/Grav/Framework/Flex/FlexDirectoryForm.php @@ -0,0 +1,509 @@ +getDirectoryForm($name, $options); + } + + /** + * FlexForm constructor. + * @param string $name + * @param FlexDirectory $directory + * @param array|null $options + */ + public function __construct(string $name, FlexDirectory $directory, array $options = null) + { + $this->name = $name; + $this->setDirectory($directory); + $this->setName($directory->getFlexType(), $name); + $this->setId($this->getName()); + + $uniqueId = $options['unique_id'] ?? null; + if (!$uniqueId) { + $uniqueId = md5($directory->getFlexType() . '-directory-' . $this->name); + } + $this->setUniqueId($uniqueId); + + $this->setFlashLookupFolder($directory->getDirectoryBlueprint()->get('form/flash_folder') ?? 'tmp://forms/[SESSIONID]'); + $this->form = $options['form'] ?? null; + + if (Utils::isPositive($this->form['disabled'] ?? false)) { + $this->disable(); + } + + $this->initialize(); + } + + /** + * @return $this + */ + public function initialize() + { + $this->messages = []; + $this->submitted = false; + $this->data = new Data($this->directory->loadDirectoryConfig($this->name), $this->getBlueprint()); + $this->files = []; + $this->unsetFlash(); + + /** @var FlexFormFlash $flash */ + $flash = $this->getFlash(); + if ($flash->exists()) { + $data = $flash->getData(); + $includeOriginal = (bool)($this->getBlueprint()->form()['images']['original'] ?? null); + + $directory = $flash->getDirectory(); + if (null === $directory) { + throw new RuntimeException('Flash has no directory'); + } + $this->directory = $directory; + $this->data = $data ? new Data($data, $this->getBlueprint()) : null; + $this->files = $flash->getFilesByFields($includeOriginal); + } + + return $this; + } + + /** + * @param string $uniqueId + * @return void + */ + public function setUniqueId(string $uniqueId): void + { + if ($uniqueId !== '') { + $this->uniqueid = $uniqueId; + } + } + + /** + * @param string $name + * @param mixed $default + * @param string|null $separator + * @return mixed + */ + public function get($name, $default = null, $separator = null) + { + switch (strtolower($name)) { + case 'id': + case 'uniqueid': + case 'name': + case 'noncename': + case 'nonceaction': + case 'action': + case 'data': + case 'files': + case 'errors'; + case 'fields': + case 'blueprint': + case 'page': + $method = 'get' . $name; + return $this->{$method}(); + } + + return $this->traitGet($name, $default, $separator); + } + + /** + * @param string $name + * @param mixed $value + * @param string|null $separator + * @return $this + */ + public function set($name, $value, $separator = null) + { + switch (strtolower($name)) { + case 'id': + case 'uniqueid': + $method = 'set' . $name; + return $this->{$method}(); + } + + return $this->traitSet($name, $value, $separator); + } + + /** + * @return string + */ + public function getName(): string + { + return $this->flexName; + } + + protected function setName(string $type, string $name): void + { + // Make sure that both type and name do not have dash (convert dashes to underscores). + $type = str_replace('-', '_', $type); + $name = str_replace('-', '_', $name); + $this->flexName = $name ? "flex_conf-{$type}-{$name}" : "flex_conf-{$type}"; + } + + /** + * @return Data|object + */ + public function getData() + { + if (null === $this->data) { + $this->data = new Data([], $this->getBlueprint()); + } + + return $this->data; + } + + /** + * Get a value from the form. + * + * Note: Used in form fields. + * + * @param string $name + * @return mixed + */ + public function getValue(string $name) + { + // Attempt to get value from the form data. + $value = $this->data ? $this->data[$name] : null; + + // Return the form data or fall back to the object property. + return $value ?? null; + } + + /** + * @param string $name + * @return array|mixed|null + */ + public function getDefaultValue(string $name) + { + return $this->getBlueprint()->getDefaultValue($name); + } + + /** + * @return array + */ + public function getDefaultValues(): array + { + return $this->getBlueprint()->getDefaults(); + } + /** + * @return string + */ + public function getFlexType(): string + { + return $this->directory->getFlexType(); + } + + /** + * Get form flash object. + * + * @return FormFlashInterface|FlexFormFlash + */ + public function getFlash() + { + if (null === $this->flash) { + $grav = Grav::instance(); + $config = [ + 'session_id' => $this->getSessionId(), + 'unique_id' => $this->getUniqueId(), + 'form_name' => $this->getName(), + 'folder' => $this->getFlashFolder(), + 'id' => $this->getFlashId(), + 'directory' => $this->getDirectory() + ]; + + $this->flash = new FlexFormFlash($config); + $this->flash + ->setUrl($grav['uri']->url) + ->setUser($grav['user'] ?? null); + } + + return $this->flash; + } + + /** + * @return FlexDirectory + */ + public function getDirectory(): FlexDirectory + { + return $this->directory; + } + + /** + * @return Blueprint + */ + public function getBlueprint(): Blueprint + { + if (null === $this->blueprint) { + try { + $blueprint = $this->getDirectory()->getDirectoryBlueprint(); + if ($this->form) { + // We have field overrides available. + $blueprint->extend(['form' => $this->form], true); + $blueprint->init(); + } + } catch (RuntimeException $e) { + if (!isset($this->form['fields'])) { + throw $e; + } + + // Blueprint is not defined, but we have custom form fields available. + $blueprint = new Blueprint(null, ['form' => $this->form]); + $blueprint->load(); + $blueprint->setScope('directory'); + $blueprint->init(); + } + + $this->blueprint = $blueprint; + } + + return $this->blueprint; + } + + /** + * @return Route|null + */ + public function getFileUploadAjaxRoute(): ?Route + { + return null; + } + + /** + * @param string|null $field + * @param string|null $filename + * @return Route|null + */ + public function getFileDeleteAjaxRoute($field = null, $filename = null): ?Route + { + return null; + } + + /** + * @param array $params + * @param string|null $extension + * @return string + */ + public function getMediaTaskRoute(array $params = [], string $extension = null): string + { + return ''; + } + + /** + * @param string $name + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function __get($name) + { + $method = "get{$name}"; + if (method_exists($this, $method)) { + return $this->{$method}(); + } + + $form = $this->getBlueprint()->form(); + + return $form[$name] ?? null; + } + + /** + * @param string $name + * @param mixed $value + * @return void + */ + #[\ReturnTypeWillChange] + public function __set($name, $value) + { + $method = "set{$name}"; + if (method_exists($this, $method)) { + $this->{$method}($value); + } + } + + /** + * @param string $name + * @return bool + */ + #[\ReturnTypeWillChange] + public function __isset($name) + { + $method = "get{$name}"; + if (method_exists($this, $method)) { + return true; + } + + $form = $this->getBlueprint()->form(); + + return isset($form[$name]); + } + + /** + * @param string $name + * @return void + */ + #[\ReturnTypeWillChange] + public function __unset($name) + { + } + + /** + * @return array|bool + */ + protected function getUnserializeAllowedClasses() + { + return [FlexObject::class]; + } + + /** + * Note: this method clones the object. + * + * @param FlexDirectory $directory + * @return $this + */ + protected function setDirectory(FlexDirectory $directory): self + { + $this->directory = $directory; + + return $this; + } + + /** + * @param string $layout + * @return Template|TemplateWrapper + * @throws LoaderError + * @throws SyntaxError + */ + protected function getTemplate($layout) + { + $grav = Grav::instance(); + + /** @var Twig $twig */ + $twig = $grav['twig']; + + return $twig->twig()->resolveTemplate( + [ + "flex-objects/layouts/{$this->getFlexType()}/form/{$layout}.html.twig", + "flex-objects/layouts/_default/form/{$layout}.html.twig", + "forms/{$layout}/form.html.twig", + 'forms/default/form.html.twig' + ] + ); + } + + /** + * @param array $data + * @param array $files + * @return void + * @throws Exception + */ + protected function doSubmit(array $data, array $files) + { + $this->directory->saveDirectoryConfig($this->name, $data); + + $this->reset(); + } + + /** + * @return array + */ + protected function doSerialize(): array + { + return $this->doTraitSerialize() + [ + 'form' => $this->form, + 'directory' => $this->directory, + 'flexName' => $this->flexName + ]; + } + + /** + * @param array $data + * @return void + */ + protected function doUnserialize(array $data): void + { + $this->doTraitUnserialize($data); + + $this->form = $data['form']; + $this->directory = $data['directory']; + $this->flexName = $data['flexName']; + } + + /** + * Filter validated data. + * + * @param ArrayAccess|Data|null $data + * @phpstan-param ArrayAccess|Data|null $data + */ + protected function filterData($data = null): void + { + if ($data instanceof Data) { + $data->filter(false, true); + } + } +} diff --git a/system/src/Grav/Framework/Flex/FlexForm.php b/system/src/Grav/Framework/Flex/FlexForm.php new file mode 100644 index 0000000..48f7157 --- /dev/null +++ b/system/src/Grav/Framework/Flex/FlexForm.php @@ -0,0 +1,610 @@ +getObject($key) ?? $directory->createObject([], $key); + } else { + throw new RuntimeException(__METHOD__ . "(): You need to pass option 'directory' or 'object'", 400); + } + + $name = $options['name'] ?? ''; + + // There is no reason to pass object and directory. + unset($options['object'], $options['directory']); + + return $object->getForm($name, $options); + } + + /** + * FlexForm constructor. + * @param string $name + * @param FlexObjectInterface $object + * @param array|null $options + */ + public function __construct(string $name, FlexObjectInterface $object, array $options = null) + { + $this->name = $name; + $this->setObject($object); + + if (isset($options['form']['name'])) { + // Use custom form name. + $this->flexName = $options['form']['name']; + } else { + // Use standard form name. + $this->setName($object->getFlexType(), $name); + } + $this->setId($this->getName()); + + $uniqueId = $options['unique_id'] ?? null; + if (!$uniqueId) { + if ($object->exists()) { + $uniqueId = $object->getStorageKey(); + } elseif ($object->hasKey()) { + $uniqueId = "{$object->getKey()}:new"; + } else { + $uniqueId = "{$object->getFlexType()}:new"; + } + $uniqueId = md5($uniqueId); + } + $this->setUniqueId($uniqueId); + + $directory = $object->getFlexDirectory(); + $this->setFlashLookupFolder($options['flash_folder'] ?? $directory->getBlueprint()->get('form/flash_folder') ?? 'tmp://forms/[SESSIONID]'); + $this->form = $options['form'] ?? null; + + if (Utils::isPositive($this->items['disabled'] ?? $this->form['disabled'] ?? false)) { + $this->disable(); + } + + if (!empty($options['reset'])) { + $this->getFlash()->delete(); + } + + $this->initialize(); + } + + /** + * @return $this + */ + public function initialize() + { + $this->messages = []; + $this->submitted = false; + $this->data = null; + $this->files = []; + $this->unsetFlash(); + + /** @var FlexFormFlash $flash */ + $flash = $this->getFlash(); + if ($flash->exists()) { + $data = $flash->getData(); + if (null !== $data) { + $data = new Data($data, $this->getBlueprint()); + $data->setKeepEmptyValues(true); + $data->setMissingValuesAsNull(true); + } + + $object = $flash->getObject(); + if (null === $object) { + throw new RuntimeException('Flash has no object'); + } + + $this->object = $object; + $this->data = $data; + + $includeOriginal = (bool)($this->getBlueprint()->form()['images']['original'] ?? null); + $this->files = $flash->getFilesByFields($includeOriginal); + } + + return $this; + } + + /** + * @param string $uniqueId + * @return void + */ + public function setUniqueId(string $uniqueId): void + { + if ($uniqueId !== '') { + $this->uniqueid = $uniqueId; + } + } + + /** + * @param string $name + * @param mixed $default + * @param string|null $separator + * @return mixed + */ + public function get($name, $default = null, $separator = null) + { + switch (strtolower($name)) { + case 'id': + case 'uniqueid': + case 'name': + case 'noncename': + case 'nonceaction': + case 'action': + case 'data': + case 'files': + case 'errors'; + case 'fields': + case 'blueprint': + case 'page': + $method = 'get' . $name; + return $this->{$method}(); + } + + return $this->traitGet($name, $default, $separator); + } + + /** + * @param string $name + * @param mixed $value + * @param string|null $separator + * @return FlexForm + */ + public function set($name, $value, $separator = null) + { + switch (strtolower($name)) { + case 'id': + case 'uniqueid': + $method = 'set' . $name; + return $this->{$method}(); + } + + return $this->traitSet($name, $value, $separator); + } + + /** + * @return string + */ + public function getName(): string + { + return $this->flexName; + } + + /** + * @param callable|null $submitMethod + */ + public function setSubmitMethod(?callable $submitMethod): void + { + $this->submitMethod = $submitMethod; + } + + /** + * @param string $type + * @param string $name + */ + protected function setName(string $type, string $name): void + { + // Make sure that both type and name do not have dash (convert dashes to underscores). + $type = str_replace('-', '_', $type); + $name = str_replace('-', '_', $name); + $this->flexName = $name ? "flex-{$type}-{$name}" : "flex-{$type}"; + } + + /** + * @return Data|FlexObjectInterface|object + */ + public function getData() + { + return $this->data ?? $this->getObject(); + } + + /** + * Get a value from the form. + * + * Note: Used in form fields. + * + * @param string $name + * @return mixed + */ + public function getValue(string $name) + { + // Attempt to get value from the form data. + $value = $this->data ? $this->data[$name] : null; + + // Return the form data or fall back to the object property. + return $value ?? $this->getObject()->getFormValue($name); + } + + /** + * @param string $name + * @return array|mixed|null + */ + public function getDefaultValue(string $name) + { + return $this->object->getDefaultValue($name); + } + + /** + * @return array + */ + public function getDefaultValues(): array + { + return $this->object->getDefaultValues(); + } + /** + * @return string + */ + public function getFlexType(): string + { + return $this->object->getFlexType(); + } + + /** + * Get form flash object. + * + * @return FormFlashInterface|FlexFormFlash + */ + public function getFlash() + { + if (null === $this->flash) { + $grav = Grav::instance(); + $config = [ + 'session_id' => $this->getSessionId(), + 'unique_id' => $this->getUniqueId(), + 'form_name' => $this->getName(), + 'folder' => $this->getFlashFolder(), + 'id' => $this->getFlashId(), + 'object' => $this->getObject() + ]; + + $this->flash = new FlexFormFlash($config); + $this->flash + ->setUrl($grav['uri']->url) + ->setUser($grav['user'] ?? null); + } + + return $this->flash; + } + + /** + * @return FlexObjectInterface + */ + public function getObject(): FlexObjectInterface + { + return $this->object; + } + + /** + * @return FlexObjectInterface + */ + public function updateObject(): FlexObjectInterface + { + $data = $this->data instanceof Data ? $this->data->toArray() : []; + $files = $this->files; + + return $this->getObject()->update($data, $files); + } + + /** + * @return Blueprint + */ + public function getBlueprint(): Blueprint + { + if (null === $this->blueprint) { + try { + $blueprint = $this->getObject()->getBlueprint($this->name); + if ($this->form) { + // We have field overrides available. + $blueprint->extend(['form' => $this->form], true); + $blueprint->init(); + } + } catch (RuntimeException $e) { + if (!isset($this->form['fields'])) { + throw $e; + } + + // Blueprint is not defined, but we have custom form fields available. + $blueprint = new Blueprint(null, ['form' => $this->form]); + $blueprint->load(); + $blueprint->setScope('object'); + $blueprint->init(); + } + + $this->blueprint = $blueprint; + } + + return $this->blueprint; + } + + /** + * @return Route|null + */ + public function getFileUploadAjaxRoute(): ?Route + { + $object = $this->getObject(); + if (!method_exists($object, 'route')) { + /** @var Route $route */ + $route = Grav::instance()['route']; + + return $route->withExtension('json')->withGravParam('task', 'media.upload'); + } + + return $object->route('/edit.json/task:media.upload'); + } + + /** + * @param string|null $field + * @param string|null $filename + * @return Route|null + */ + public function getFileDeleteAjaxRoute($field = null, $filename = null): ?Route + { + $object = $this->getObject(); + if (!method_exists($object, 'route')) { + /** @var Route $route */ + $route = Grav::instance()['route']; + + return $route->withExtension('json')->withGravParam('task', 'media.delete'); + } + + return $object->route('/edit.json/task:media.delete'); + } + + /** + * @param array $params + * @param string|null $extension + * @return string + */ + public function getMediaTaskRoute(array $params = [], string $extension = null): string + { + $grav = Grav::instance(); + /** @var Flex $flex */ + $flex = $grav['flex_objects']; + + if (method_exists($flex, 'adminRoute')) { + return $flex->adminRoute($this->getObject(), $params, $extension ?? 'json'); + } + + return ''; + } + + /** + * @param string $name + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function __get($name) + { + $method = "get{$name}"; + if (method_exists($this, $method)) { + return $this->{$method}(); + } + + $form = $this->getBlueprint()->form(); + + return $form[$name] ?? null; + } + + /** + * @param string $name + * @param mixed $value + * @return void + */ + #[\ReturnTypeWillChange] + public function __set($name, $value) + { + $method = "set{$name}"; + if (method_exists($this, $method)) { + $this->{$method}($value); + } + } + + /** + * @param string $name + * @return bool + */ + #[\ReturnTypeWillChange] + public function __isset($name) + { + $method = "get{$name}"; + if (method_exists($this, $method)) { + return true; + } + + $form = $this->getBlueprint()->form(); + + return isset($form[$name]); + } + + /** + * @param string $name + * @return void + */ + #[\ReturnTypeWillChange] + public function __unset($name) + { + } + + /** + * @return array|bool + */ + protected function getUnserializeAllowedClasses() + { + return [FlexObject::class]; + } + + /** + * Note: this method clones the object. + * + * @param FlexObjectInterface $object + * @return $this + */ + protected function setObject(FlexObjectInterface $object): self + { + $this->object = clone $object; + + return $this; + } + + /** + * @param string $layout + * @return Template|TemplateWrapper + * @throws LoaderError + * @throws SyntaxError + */ + protected function getTemplate($layout) + { + $grav = Grav::instance(); + + /** @var Twig $twig */ + $twig = $grav['twig']; + + return $twig->twig()->resolveTemplate( + [ + "flex-objects/layouts/{$this->getFlexType()}/form/{$layout}.html.twig", + "flex-objects/layouts/_default/form/{$layout}.html.twig", + "forms/{$layout}/form.html.twig", + 'forms/default/form.html.twig' + ] + ); + } + + /** + * @param array $data + * @param array $files + * @return void + * @throws Exception + */ + protected function doSubmit(array $data, array $files) + { + /** @var FlexObject $object */ + $object = clone $this->getObject(); + + $method = $this->submitMethod; + if ($method) { + $method($data, $files, $object); + } else { + $object->update($data, $files); + $object->save(); + } + + $this->setObject($object); + $this->reset(); + } + + /** + * @return array + */ + protected function doSerialize(): array + { + return $this->doTraitSerialize() + [ + 'items' => $this->items, + 'form' => $this->form, + 'object' => $this->object, + 'flexName' => $this->flexName, + 'submitMethod' => $this->submitMethod, + ]; + } + + /** + * @param array $data + * @return void + */ + protected function doUnserialize(array $data): void + { + $this->doTraitUnserialize($data); + + $this->items = $data['items'] ?? null; + $this->form = $data['form'] ?? null; + $this->object = $data['object'] ?? null; + $this->flexName = $data['flexName'] ?? null; + $this->submitMethod = $data['submitMethod'] ?? null; + } + + /** + * Filter validated data. + * + * @param ArrayAccess|Data|null $data + * @return void + * @phpstan-param ArrayAccess|Data|null $data + */ + protected function filterData($data = null): void + { + if ($data instanceof Data) { + $data->filter(true, true); + } + } +} diff --git a/system/src/Grav/Framework/Flex/FlexFormFlash.php b/system/src/Grav/Framework/Flex/FlexFormFlash.php new file mode 100644 index 0000000..e9b6e6f --- /dev/null +++ b/system/src/Grav/Framework/Flex/FlexFormFlash.php @@ -0,0 +1,130 @@ +object = $object; + $this->directory = $object->getFlexDirectory(); + } + + /** + * @return FlexObjectInterface|null + */ + public function getObject(): ?FlexObjectInterface + { + return $this->object; + } + + /** + * @param FlexDirectory $directory + */ + public function setDirectory(FlexDirectory $directory): void + { + $this->directory = $directory; + } + + /** + * @return FlexDirectory|null + */ + public function getDirectory(): ?FlexDirectory + { + return $this->directory; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $serialized = parent::jsonSerialize(); + + $object = $this->getObject(); + if ($object instanceof FlexObjectInterface) { + $serialized['object'] = [ + 'type' => $object->getFlexType(), + 'key' => $object->getKey() ?: null, + 'storage_key' => $object->getStorageKey(), + 'timestamp' => $object->getTimestamp(), + 'serialized' => $object->prepareStorage() + ]; + } else { + $directory = $this->getDirectory(); + if ($directory instanceof FlexDirectory) { + $serialized['directory'] = [ + 'type' => $directory->getFlexType() + ]; + } + } + + return $serialized; + } + + /** + * @param array|null $data + * @param array $config + * @return void + */ + protected function init(?array $data, array $config): void + { + parent::init($data, $config); + + $data = $data ?? []; + /** @var FlexObjectInterface|null $object */ + $object = $config['object'] ?? null; + $create = true; + if ($object) { + $directory = $object->getFlexDirectory(); + $create = !$object->exists(); + } elseif (null === ($directory = $config['directory'] ?? null)) { + $flex = $config['flex'] ?? static::$flex; + $type = $data['object']['type'] ?? $data['directory']['type'] ?? null; + $directory = $flex && $type ? $flex->getDirectory($type) : null; + } + + if ($directory && $create && isset($data['object']['serialized'])) { + // TODO: update instead of create new. + $object = $directory->createObject($data['object']['serialized'], $data['object']['key'] ?? ''); + } + + if ($object) { + $this->setObject($object); + } elseif ($directory) { + $this->setDirectory($directory); + } + } +} diff --git a/system/src/Grav/Framework/Flex/FlexIdentifier.php b/system/src/Grav/Framework/Flex/FlexIdentifier.php new file mode 100644 index 0000000..ec47ed8 --- /dev/null +++ b/system/src/Grav/Framework/Flex/FlexIdentifier.php @@ -0,0 +1,75 @@ + + */ +class FlexIdentifier extends Identifier +{ + /** @var string */ + private $keyField; + /** @var FlexObjectInterface|null */ + private $object = null; + + /** + * @param FlexObjectInterface $object + * @return FlexIdentifier + */ + public static function createFromObject(FlexObjectInterface $object): FlexIdentifier + { + $instance = new static($object->getKey(), $object->getFlexType(), 'key'); + $instance->setObject($object); + + return $instance; + } + + /** + * IdentifierInterface constructor. + * @param string $id + * @param string $type + * @param string $keyField + */ + public function __construct(string $id, string $type, string $keyField = 'key') + { + parent::__construct($id, $type); + + $this->keyField = $keyField; + } + + /** + * @return T + */ + public function getObject(): ?FlexObjectInterface + { + if (!isset($this->object)) { + /** @var Flex $flex */ + $flex = Grav::instance()['flex']; + + $this->object = $flex->getObject($this->getId(), $this->getType(), $this->keyField); + } + + return $this->object; + } + + /** + * @param T $object + */ + public function setObject(FlexObjectInterface $object): void + { + $type = $this->getType(); + if ($type !== $object->getFlexType()) { + throw new RuntimeException(sprintf('Object has to be type %s, %s given', $type, $object->getFlexType())); + } + + $this->object = $object; + } +} diff --git a/system/src/Grav/Framework/Flex/FlexIndex.php b/system/src/Grav/Framework/Flex/FlexIndex.php new file mode 100644 index 0000000..b96fed1 --- /dev/null +++ b/system/src/Grav/Framework/Flex/FlexIndex.php @@ -0,0 +1,930 @@ + + * @implements FlexIndexInterface + * @mixin C + */ +class FlexIndex extends ObjectIndex implements FlexIndexInterface +{ + const VERSION = 1; + + /** @var FlexDirectory|null */ + private $_flexDirectory; + /** @var string */ + private $_keyField = 'storage_key'; + /** @var array */ + private $_indexKeys; + + /** + * @param FlexDirectory $directory + * @return static + * @phpstan-return static + */ + public static function createFromStorage(FlexDirectory $directory) + { + return static::createFromArray(static::loadEntriesFromStorage($directory->getStorage()), $directory); + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::createFromArray() + */ + public static function createFromArray(array $entries, FlexDirectory $directory, string $keyField = null) + { + $instance = new static($entries, $directory); + $instance->setKeyField($keyField); + + return $instance; + } + + /** + * @param FlexStorageInterface $storage + * @return array + */ + public static function loadEntriesFromStorage(FlexStorageInterface $storage): array + { + return $storage->getExistingKeys(); + } + + /** + * You can define indexes for fast lookup. + * + * Primary key: $meta['key'] + * Secondary keys: $meta['my_field'] + * + * @param array $meta + * @param array $data + * @param FlexStorageInterface $storage + * @return void + */ + public static function updateObjectMeta(array &$meta, array $data, FlexStorageInterface $storage) + { + // For backwards compatibility, no need to call this method when you override this method. + static::updateIndexData($meta, $data); + } + + /** + * Initializes a new FlexIndex. + * + * @param array $entries + * @param FlexDirectory|null $directory + */ + public function __construct(array $entries = [], FlexDirectory $directory = null) + { + // @phpstan-ignore-next-line + if (get_class($this) === __CLASS__) { + user_error('Using ' . __CLASS__ . ' directly is deprecated since Grav 1.7, use \Grav\Common\Flex\Types\Generic\GenericIndex or your own class instead', E_USER_DEPRECATED); + } + + parent::__construct($entries); + + $this->_flexDirectory = $directory; + $this->setKeyField(null); + } + + /** + * @return string + */ + public function getKey() + { + return $this->_key ?: $this->getFlexType() . '@@' . spl_object_hash($this); + } + + /** + * {@inheritdoc} + * @see FlexCommonInterface::hasFlexFeature() + */ + public function hasFlexFeature(string $name): bool + { + return in_array($name, $this->getFlexFeatures(), true); + } + + /** + * {@inheritdoc} + * @see FlexCommonInterface::hasFlexFeature() + */ + public function getFlexFeatures(): array + { + /** @var array $implements */ + $implements = class_implements($this->getFlexDirectory()->getCollectionClass()); + + $list = []; + foreach ($implements as $interface) { + if ($pos = strrpos($interface, '\\')) { + $interface = substr($interface, $pos+1); + } + + $list[] = Inflector::hyphenize(str_replace('Interface', '', $interface)); + } + + return $list; + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::search() + */ + public function search(string $search, $properties = null, array $options = null) + { + $directory = $this->getFlexDirectory(); + $properties = $directory->getSearchProperties($properties); + $options = $directory->getSearchOptions($options); + + return $this->__call('search', [$search, $properties, $options]); + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::sort() + */ + public function sort(array $orderings) + { + return $this->orderBy($orderings); + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::filterBy() + */ + public function filterBy(array $filters) + { + return $this->__call('filterBy', [$filters]); + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::getFlexType() + */ + public function getFlexType(): string + { + return $this->getFlexDirectory()->getFlexType(); + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::getFlexDirectory() + */ + public function getFlexDirectory(): FlexDirectory + { + if (null === $this->_flexDirectory) { + throw new RuntimeException('Flex Directory not defined, object is not fully defined'); + } + + return $this->_flexDirectory; + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::getTimestamp() + */ + public function getTimestamp(): int + { + $timestamps = $this->getTimestamps(); + + return $timestamps ? max($timestamps) : time(); + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::getCacheKey() + */ + public function getCacheKey(): string + { + return $this->getTypePrefix() . $this->getFlexType() . '.' . sha1(json_encode($this->getKeys()) . $this->_keyField); + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::getCacheChecksum() + */ + public function getCacheChecksum(): string + { + $list = []; + foreach ($this->getEntries() as $key => $value) { + $list[$key] = $value['checksum'] ?? $value['storage_timestamp']; + } + + return sha1((string)json_encode($list)); + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::getTimestamps() + */ + public function getTimestamps(): array + { + return $this->getIndexMap('storage_timestamp'); + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::getStorageKeys() + */ + public function getStorageKeys(): array + { + return $this->getIndexMap('storage_key'); + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::getFlexKeys() + */ + public function getFlexKeys(): array + { + // Get storage keys for the objects. + $keys = []; + $type = $this->getFlexDirectory()->getFlexType() . '.obj:'; + + foreach ($this->getEntries() as $key => $value) { + $keys[$key] = $value['flex_key'] ?? $type . $value['storage_key']; + } + + return $keys; + } + + /** + * {@inheritdoc} + * @see FlexIndexInterface::withKeyField() + */ + public function withKeyField(string $keyField = null) + { + $keyField = $keyField ?: 'key'; + if ($keyField === $this->getKeyField()) { + return $this; + } + + $type = $keyField === 'flex_key' ? $this->getFlexDirectory()->getFlexType() . '.obj:' : ''; + $entries = []; + foreach ($this->getEntries() as $key => $value) { + if (!isset($value['key'])) { + $value['key'] = $key; + } + + if (isset($value[$keyField])) { + $entries[$value[$keyField]] = $value; + } elseif ($keyField === 'flex_key') { + $entries[$type . $value['storage_key']] = $value; + } + } + + return $this->createFrom($entries, $keyField); + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::getIndex() + */ + public function getIndex() + { + return $this; + } + + /** + * @return FlexCollectionInterface + * @phpstan-return C + */ + public function getCollection() + { + return $this->loadCollection(); + } + + /** + * {@inheritdoc} + * @see FlexCollectionInterface::render() + */ + public function render(string $layout = null, array $context = []) + { + return $this->__call('render', [$layout, $context]); + } + + /** + * {@inheritdoc} + * @see FlexIndexInterface::getFlexKeys() + */ + public function getIndexMap(string $indexKey = null) + { + if (null === $indexKey) { + return $this->getEntries(); + } + + // Get storage keys for the objects. + $index = []; + foreach ($this->getEntries() as $key => $value) { + $index[$key] = $value[$indexKey] ?? null; + } + + return $index; + } + + /** + * @param string $key + * @return array + */ + public function getMetaData($key): array + { + return $this->getEntries()[$key] ?? []; + } + + /** + * @return string + */ + public function getKeyField(): string + { + return $this->_keyField; + } + + /** + * @param string|null $namespace + * @return CacheInterface + */ + public function getCache(string $namespace = null) + { + return $this->getFlexDirectory()->getCache($namespace); + } + + /** + * @param array $orderings + * @return static + * @phpstan-return static + */ + public function orderBy(array $orderings) + { + if (!$orderings || !$this->count()) { + return $this; + } + + // Handle primary key alias. + $keyField = $this->getFlexDirectory()->getStorage()->getKeyField(); + if ($keyField !== 'key' && $keyField !== 'storage_key' && isset($orderings[$keyField])) { + $orderings['key'] = $orderings[$keyField]; + unset($orderings[$keyField]); + } + + // Check if ordering needs to load the objects. + if (array_diff_key($orderings, $this->getIndexKeys())) { + return $this->__call('orderBy', [$orderings]); + } + + // Ordering can be done by using index only. + $previous = null; + foreach (array_reverse($orderings) as $field => $ordering) { + $field = (string)$field; + if ($this->getKeyField() === $field) { + $keys = $this->getKeys(); + $search = array_combine($keys, $keys) ?: []; + } elseif ($field === 'flex_key') { + $search = $this->getFlexKeys(); + } else { + $search = $this->getIndexMap($field); + } + + // Update current search to match the previous ordering. + if (null !== $previous) { + $search = array_replace($previous, $search); + } + + // Order by current field. + if (strtoupper($ordering) === 'DESC') { + arsort($search, SORT_NATURAL | SORT_FLAG_CASE); + } else { + asort($search, SORT_NATURAL | SORT_FLAG_CASE); + } + + $previous = $search; + } + + return $this->createFrom(array_replace($previous ?? [], $this->getEntries())); + } + + /** + * {@inheritDoc} + */ + public function call($method, array $arguments = []) + { + return $this->__call('call', [$method, $arguments]); + } + + /** + * @param string $name + * @param array $arguments + * @return mixed + */ + #[\ReturnTypeWillChange] + public function __call($name, $arguments) + { + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + + /** @phpstan-var class-string $className */ + $className = $this->getFlexDirectory()->getCollectionClass(); + $cachedMethods = $className::getCachedMethods(); + + $flexType = $this->getFlexType(); + + if (!empty($cachedMethods[$name])) { + $type = $cachedMethods[$name]; + if ($type === 'session') { + /** @var Session $session */ + $session = Grav::instance()['session']; + $cacheKey = $session->getId() . ($session->user->username ?? ''); + } else { + $cacheKey = ''; + } + $key = "{$flexType}.idx." . sha1($name . '.' . $cacheKey . json_encode($arguments) . $this->getCacheKey()); + $checksum = $this->getCacheChecksum(); + + $cache = $this->getCache('object'); + + try { + $cached = $cache->get($key); + $test = $cached[0] ?? null; + $result = $test === $checksum ? ($cached[1] ?? null) : null; + + // Make sure the keys aren't changed if the returned type is the same index type. + if ($result instanceof self && $flexType === $result->getFlexType()) { + $result = $result->withKeyField($this->getKeyField()); + } + } catch (InvalidArgumentException $e) { + $debugger->addException($e); + } + + if (!isset($result)) { + $collection = $this->loadCollection(); + $result = $collection->{$name}(...$arguments); + $debugger->addMessage("Cache miss: '{$flexType}::{$name}()'", 'debug'); + + try { + // If flex collection is returned, convert it back to flex index. + if ($result instanceof FlexCollection) { + $cached = $result->getFlexDirectory()->getIndex($result->getKeys(), $this->getKeyField()); + } else { + $cached = $result; + } + + $cache->set($key, [$checksum, $cached]); + } catch (InvalidArgumentException $e) { + $debugger->addException($e); + + // TODO: log error. + } + } + } else { + $collection = $this->loadCollection(); + if (\is_callable([$collection, $name])) { + $result = $collection->{$name}(...$arguments); + if (!isset($cachedMethods[$name])) { + $debugger->addMessage("Call '{$flexType}:{$name}()' isn't cached", 'debug'); + } + } else { + $result = null; + } + } + + return $result; + } + + /** + * @return array + */ + public function __serialize(): array + { + return ['type' => $this->getFlexType(), 'entries' => $this->getEntries()]; + } + + /** + * @param array $data + * @return void + */ + public function __unserialize(array $data): void + { + $this->_flexDirectory = Grav::instance()['flex']->getDirectory($data['type']); + $this->setEntries($data['entries']); + } + + /** + * @return array + */ + #[\ReturnTypeWillChange] + public function __debugInfo() + { + return [ + 'type:private' => $this->getFlexType(), + 'key:private' => $this->getKey(), + 'entries_key:private' => $this->getKeyField(), + 'entries:private' => $this->getEntries() + ]; + } + + /** + * @param array $entries + * @param string|null $keyField + * @return static + * @phpstan-return static + */ + protected function createFrom(array $entries, string $keyField = null) + { + /** @phpstan-var static $index */ + $index = new static($entries, $this->getFlexDirectory()); + $index->setKeyField($keyField ?? $this->_keyField); + + return $index; + } + + /** + * @param string|null $keyField + * @return void + */ + protected function setKeyField(string $keyField = null) + { + $this->_keyField = $keyField ?? 'storage_key'; + } + + /** + * @return array + */ + protected function getIndexKeys() + { + if (null === $this->_indexKeys) { + $entries = $this->getEntries(); + $first = reset($entries); + if ($first) { + $keys = array_keys($first); + $keys = array_combine($keys, $keys) ?: []; + } else { + $keys = []; + } + + $this->setIndexKeys($keys); + } + + return $this->_indexKeys; + } + + /** + * @param array $indexKeys + * @return void + */ + protected function setIndexKeys(array $indexKeys) + { + // Add defaults. + $indexKeys += [ + 'key' => 'key', + 'storage_key' => 'storage_key', + 'storage_timestamp' => 'storage_timestamp', + 'flex_key' => 'flex_key' + ]; + + + $this->_indexKeys = $indexKeys; + } + + /** + * @return string + */ + protected function getTypePrefix() + { + return 'i.'; + } + + /** + * @param string $key + * @param mixed $value + * @return ObjectInterface|null + * @phpstan-return T|null + */ + protected function loadElement($key, $value): ?ObjectInterface + { + /** @phpstan-var T[] $objects */ + $objects = $this->getFlexDirectory()->loadObjects([$key => $value]); + + return $objects ? reset($objects): null; + } + + /** + * @param array|null $entries + * @return ObjectInterface[] + * @phpstan-return T[] + */ + protected function loadElements(array $entries = null): array + { + /** @phpstan-var T[] $objects */ + $objects = $this->getFlexDirectory()->loadObjects($entries ?? $this->getEntries()); + + return $objects; + } + + /** + * @param array|null $entries + * @return CollectionInterface + * @phpstan-return C + */ + protected function loadCollection(array $entries = null): CollectionInterface + { + /** @var C $collection */ + $collection = $this->getFlexDirectory()->loadCollection($entries ?? $this->getEntries(), $this->_keyField); + + return $collection; + } + + /** + * @param mixed $value + * @return bool + */ + protected function isAllowedElement($value): bool + { + return $value instanceof FlexObject; + } + + /** + * @param FlexObjectInterface $object + * @return mixed + */ + protected function getElementMeta($object) + { + return $object->getMetaData(); + } + + /** + * @param FlexObjectInterface $element + * @return string + */ + protected function getCurrentKey($element) + { + $keyField = $this->getKeyField(); + if ($keyField === 'storage_key') { + return $element->getStorageKey(); + } + if ($keyField === 'flex_key') { + return $element->getFlexKey(); + } + if ($keyField === 'key') { + return $element->getKey(); + } + + return $element->getKey(); + } + + /** + * @param FlexStorageInterface $storage + * @param array $index Saved index + * @param array $entries Updated index + * @param array $options + * @return array Compiled list of entries + */ + protected static function updateIndexFile(FlexStorageInterface $storage, array $index, array $entries, array $options = []): array + { + $indexFile = static::getIndexFile($storage); + if (null === $indexFile) { + return $entries; + } + + // Calculate removed objects. + $removed = array_diff_key($index, $entries); + + // First get rid of all removed objects. + if ($removed) { + $index = array_diff_key($index, $removed); + } + + if ($entries && empty($options['force_update'])) { + // Calculate difference between saved index and current data. + foreach ($index as $key => $entry) { + $storage_key = $entry['storage_key'] ?? null; + if (isset($entries[$storage_key]) && $entries[$storage_key]['storage_timestamp'] === $entry['storage_timestamp']) { + // Entry is up to date, no update needed. + unset($entries[$storage_key]); + } + } + + if (empty($entries) && empty($removed)) { + // No objects were added, updated or removed. + return $index; + } + } elseif (!$removed) { + // There are no objects and nothing was removed. + return []; + } + + // Index should be updated, lock the index file for saving. + $indexFile->lock(); + + // Read all the data rows into an array using chunks of 100. + $keys = array_fill_keys(array_keys($entries), null); + $chunks = array_chunk($keys, 100, true); + $updated = $added = []; + foreach ($chunks as $keys) { + $rows = $storage->readRows($keys); + + $keyField = $storage->getKeyField(); + + // Go through all the updated objects and refresh their index data. + foreach ($rows as $key => $row) { + if (null !== $row || !empty($options['include_missing'])) { + $entry = $entries[$key] + ['key' => $key]; + if ($keyField !== 'storage_key' && isset($row[$keyField])) { + $entry['key'] = $row[$keyField]; + } + static::updateObjectMeta($entry, $row ?? [], $storage); + if (isset($row['__ERROR'])) { + $entry['__ERROR'] = true; + static::onException(new RuntimeException(sprintf('Object failed to load: %s (%s)', $key, + $row['__ERROR']))); + } + if (isset($index[$key])) { + // Update object in the index. + $updated[$key] = $entry; + } else { + // Add object into the index. + $added[$key] = $entry; + } + + // Either way, update the entry. + $index[$key] = $entry; + } elseif (isset($index[$key])) { + // Remove object from the index. + $removed[$key] = $index[$key]; + unset($index[$key]); + } + } + unset($rows); + } + + // Sort the index before saving it. + ksort($index, SORT_NATURAL | SORT_FLAG_CASE); + + static::onChanges($index, $added, $updated, $removed); + + $indexFile->save(['version' => static::VERSION, 'timestamp' => time(), 'count' => count($index), 'index' => $index]); + $indexFile->unlock(); + + return $index; + } + + /** + * @param array $entry + * @param array $data + * @return void + * @deprecated 1.7 Use static ::updateObjectMeta() method instead. + */ + protected static function updateIndexData(array &$entry, array $data) + { + } + + /** + * @param FlexStorageInterface $storage + * @return array + */ + protected static function loadIndex(FlexStorageInterface $storage) + { + $indexFile = static::getIndexFile($storage); + + if ($indexFile) { + $data = []; + try { + $data = (array)$indexFile->content(); + $version = $data['version'] ?? null; + if ($version !== static::VERSION) { + $data = []; + } + } catch (Exception $e) { + $e = new RuntimeException(sprintf('Index failed to load: %s', $e->getMessage()), $e->getCode(), $e); + + static::onException($e); + } + + if ($data) { + return $data; + } + } + + return ['version' => static::VERSION, 'timestamp' => 0, 'count' => 0, 'index' => []]; + } + + /** + * @param FlexStorageInterface $storage + * @return array + */ + protected static function loadEntriesFromIndex(FlexStorageInterface $storage) + { + $data = static::loadIndex($storage); + + return $data['index'] ?? []; + } + + /** + * @param FlexStorageInterface $storage + * @return CompiledYamlFile|CompiledJsonFile|null + */ + protected static function getIndexFile(FlexStorageInterface $storage) + { + if (!method_exists($storage, 'isIndexed') || !$storage->isIndexed()) { + return null; + } + + $path = $storage->getStoragePath(); + if (!$path) { + return null; + } + + // Load saved index file. + $grav = Grav::instance(); + $locator = $grav['locator']; + $filename = $locator->findResource("{$path}/index.yaml", true, true); + + return CompiledYamlFile::instance($filename); + } + + /** + * @param Exception $e + * @return void + */ + protected static function onException(Exception $e) + { + $grav = Grav::instance(); + + /** @var Logger $logger */ + $logger = $grav['log']; + $logger->addAlert($e->getMessage()); + + /** @var Debugger $debugger */ + $debugger = $grav['debugger']; + $debugger->addException($e); + $debugger->addMessage($e, 'error'); + } + + /** + * @param array $entries + * @param array $added + * @param array $updated + * @param array $removed + * @return void + */ + protected static function onChanges(array $entries, array $added, array $updated, array $removed) + { + $addedCount = count($added); + $updatedCount = count($updated); + $removedCount = count($removed); + + if ($addedCount + $updatedCount + $removedCount) { + $message = sprintf('Index updated, %d objects (%d added, %d updated, %d removed).', count($entries), $addedCount, $updatedCount, $removedCount); + + $grav = Grav::instance(); + + /** @var Debugger $debugger */ + $debugger = $grav['debugger']; + $debugger->addMessage($message, 'debug'); + } + } + + // DEPRECATED METHODS + + /** + * @param bool $prefix + * @return string + * @deprecated 1.6 Use `->getFlexType()` instead. + */ + public function getType($prefix = false) + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->getFlexType() method instead', E_USER_DEPRECATED); + + $type = $prefix ? $this->getTypePrefix() : ''; + + return $type . $this->getFlexType(); + } +} diff --git a/system/src/Grav/Framework/Flex/FlexObject.php b/system/src/Grav/Framework/Flex/FlexObject.php new file mode 100644 index 0000000..600c198 --- /dev/null +++ b/system/src/Grav/Framework/Flex/FlexObject.php @@ -0,0 +1,1288 @@ + true, + 'getType' => true, + 'getFlexType' => true, + 'getFlexDirectory' => true, + 'hasFlexFeature' => true, + 'getFlexFeatures' => true, + 'getCacheKey' => true, + 'getCacheChecksum' => false, + 'getTimestamp' => true, + 'value' => true, + 'exists' => true, + 'hasProperty' => true, + 'getProperty' => true, + + // FlexAclTrait + 'isAuthorized' => 'session', + ]; + } + + /** + * @param array $elements + * @param array $storage + * @param FlexDirectory $directory + * @param bool $validate + * @return static + */ + public static function createFromStorage(array $elements, array $storage, FlexDirectory $directory, bool $validate = false) + { + $instance = new static($elements, $storage['key'], $directory, $validate); + $instance->setMetaData($storage); + + return $instance; + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::__construct() + */ + public function __construct(array $elements, $key, FlexDirectory $directory, bool $validate = false) + { + if (get_class($this) === __CLASS__) { + user_error('Using ' . __CLASS__ . ' directly is deprecated since Grav 1.7, use \Grav\Common\Flex\Types\Generic\GenericObject or your own class instead', E_USER_DEPRECATED); + } + + $this->_flexDirectory = $directory; + + if (isset($elements['__META'])) { + $this->setMetaData($elements['__META']); + unset($elements['__META']); + } + + if ($validate) { + $blueprint = $this->getFlexDirectory()->getBlueprint(); + + $blueprint->validate($elements, ['xss_check' => false]); + + $elements = $blueprint->filter($elements, true, true); + } + + $this->filterElements($elements); + + $this->objectConstruct($elements, $key); + } + + /** + * {@inheritdoc} + * @see FlexCommonInterface::hasFlexFeature() + */ + public function hasFlexFeature(string $name): bool + { + return in_array($name, $this->getFlexFeatures(), true); + } + + /** + * {@inheritdoc} + * @see FlexCommonInterface::hasFlexFeature() + */ + public function getFlexFeatures(): array + { + /** @var array $implements */ + $implements = class_implements($this); + + $list = []; + foreach ($implements as $interface) { + if ($pos = strrpos($interface, '\\')) { + $interface = substr($interface, $pos+1); + } + + $list[] = Inflector::hyphenize(str_replace('Interface', '', $interface)); + } + + return $list; + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::getFlexType() + */ + public function getFlexType(): string + { + return $this->_flexDirectory->getFlexType(); + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::getFlexDirectory() + */ + public function getFlexDirectory(): FlexDirectory + { + return $this->_flexDirectory; + } + + /** + * Refresh object from the storage. + * + * @param bool $keepMissing + * @return bool True if the object was refreshed + */ + public function refresh(bool $keepMissing = false): bool + { + $key = $this->getStorageKey(); + if ('' === $key) { + return false; + } + + $storage = $this->getFlexDirectory()->getStorage(); + $meta = $storage->getMetaData([$key])[$key] ?? null; + + $newChecksum = $meta['checksum'] ?? $meta['storage_timestamp'] ?? null; + $curChecksum = $this->_meta['checksum'] ?? $this->_meta['storage_timestamp'] ?? null; + + // Check if object is up to date with the storage. + if (null === $newChecksum || $newChecksum === $curChecksum) { + return false; + } + + // Get current elements (if requested). + $current = $keepMissing ? $this->getElements() : []; + // Get elements from the filesystem. + $elements = $storage->readRows([$key => null])[$key] ?? null; + if (null !== $elements) { + $meta = $elements['__META'] ?? $meta; + unset($elements['__META']); + $this->filterElements($elements); + $newKey = $meta['key'] ?? $this->getKey(); + if ($meta) { + $this->setMetaData($meta); + } + $this->objectConstruct($elements, $newKey); + + if ($current) { + // Inject back elements which are missing in the filesystem. + $data = $this->getBlueprint()->flattenData($current); + foreach ($data as $property => $value) { + if (strpos($property, '.') === false) { + $this->defProperty($property, $value); + } else { + $this->defNestedProperty($property, $value); + } + } + } + + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + $debugger->addMessage("Refreshed {$this->getFlexType()} object {$this->getKey()}", 'debug'); + } + + return true; + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::getTimestamp() + */ + public function getTimestamp(): int + { + return $this->_meta['storage_timestamp'] ?? 0; + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::getCacheKey() + */ + public function getCacheKey(): string + { + return $this->hasKey() ? $this->getTypePrefix() . $this->getFlexType() . '.' . $this->getKey() : ''; + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::getCacheChecksum() + */ + public function getCacheChecksum(): string + { + return (string)($this->_meta['checksum'] ?? $this->getTimestamp()); + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::search() + */ + public function search(string $search, $properties = null, array $options = null): float + { + $directory = $this->getFlexDirectory(); + $properties = $directory->getSearchProperties($properties); + $options = $directory->getSearchOptions($options); + + $weight = 0; + foreach ($properties as $property) { + if (strpos($property, '.')) { + $weight += $this->searchNestedProperty($property, $search, $options); + } else { + $weight += $this->searchProperty($property, $search, $options); + } + } + + return $weight > 0 ? min($weight, 1) : 0; + } + + /** + * {@inheritdoc} + * @see ObjectInterface::getFlexKey() + */ + public function getKey() + { + return (string)$this->_key; + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::getFlexKey() + */ + public function getFlexKey(): string + { + $key = $this->_meta['flex_key'] ?? null; + + if (!$key && $key = $this->getStorageKey()) { + $key = $this->_flexDirectory->getFlexType() . '.obj:' . $key; + } + + return (string)$key; + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::getStorageKey() + */ + public function getStorageKey(): string + { + return (string)($this->storage_key ?? $this->_meta['storage_key'] ?? null); + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::getMetaData() + */ + public function getMetaData(): array + { + return $this->_meta ?? []; + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::exists() + */ + public function exists(): bool + { + $key = $this->getStorageKey(); + + return $key && $this->getFlexDirectory()->getStorage()->hasKey($key); + } + + /** + * @param string $property + * @param string $search + * @param array|null $options + * @return float + */ + public function searchProperty(string $property, string $search, array $options = null): float + { + $options = $options ?? (array)$this->getFlexDirectory()->getConfig('data.search.options'); + $value = $this->getProperty($property); + + return $this->searchValue($property, $value, $search, $options); + } + + /** + * @param string $property + * @param string $search + * @param array|null $options + * @return float + */ + public function searchNestedProperty(string $property, string $search, array $options = null): float + { + $options = $options ?? (array)$this->getFlexDirectory()->getConfig('data.search.options'); + if ($property === 'key') { + $value = $this->getKey(); + } else { + $value = $this->getNestedProperty($property); + } + + return $this->searchValue($property, $value, $search, $options); + } + + /** + * @param string $name + * @param mixed $value + * @param string $search + * @param array|null $options + * @return float + */ + protected function searchValue(string $name, $value, string $search, array $options = null): float + { + $options = $options ?? []; + + // Ignore empty search strings. + $search = trim($search); + if ($search === '') { + return 0; + } + + // Search only non-empty string values. + if (!is_string($value) || $value === '') { + return 0; + } + + $caseSensitive = $options['case_sensitive'] ?? false; + + $tested = false; + if (($tested |= !empty($options['same_as']))) { + if ($caseSensitive) { + if ($value === $search) { + return (float)$options['same_as']; + } + } elseif (mb_strtolower($value) === mb_strtolower($search)) { + return (float)$options['same_as']; + } + } + if (($tested |= !empty($options['starts_with'])) && Utils::startsWith($value, $search, $caseSensitive)) { + return (float)$options['starts_with']; + } + if (($tested |= !empty($options['ends_with'])) && Utils::endsWith($value, $search, $caseSensitive)) { + return (float)$options['ends_with']; + } + if ((!$tested || !empty($options['contains'])) && Utils::contains($value, $search, $caseSensitive)) { + return (float)($options['contains'] ?? 1); + } + + return 0; + } + + /** + * Get original data before update + * + * @return array + */ + public function getOriginalData(): array + { + return $this->_original ?? []; + } + + /** + * Get diff array from the object. + * + * @return array + */ + public function getDiff(): array + { + $blueprint = $this->getBlueprint(); + + $flattenOriginal = $blueprint->flattenData($this->getOriginalData()); + $flattenElements = $blueprint->flattenData($this->getElements()); + $removedElements = array_diff_key($flattenOriginal, $flattenElements); + $diff = []; + + // Include all added or changed keys. + foreach ($flattenElements as $key => $value) { + $orig = $flattenOriginal[$key] ?? null; + if ($orig !== $value) { + $diff[$key] = ['old' => $orig, 'new' => $value]; + } + } + + // Include all removed keys. + foreach ($removedElements as $key => $value) { + $diff[$key] = ['old' => $value, 'new' => null]; + } + + return $diff; + } + + /** + * Get any changes from the object. + * + * @return array + */ + public function getChanges(): array + { + $diff = $this->getDiff(); + + $data = new Data(); + foreach ($diff as $key => $change) { + $data->set($key, $change['new']); + } + + return $data->toArray(); + } + + /** + * @return string + */ + protected function getTypePrefix(): string + { + return 'o.'; + } + + /** + * Alias of getBlueprint() + * + * @return Blueprint + * @deprecated 1.6 Admin compatibility + */ + public function blueprints() + { + return $this->getBlueprint(); + } + + /** + * @param string|null $namespace + * @return CacheInterface + */ + public function getCache(string $namespace = null) + { + return $this->_flexDirectory->getCache($namespace); + } + + /** + * @param string|null $key + * @return $this + */ + public function setStorageKey($key = null) + { + $this->storage_key = $key ?? ''; + + return $this; + } + + /** + * @param int $timestamp + * @return $this + */ + public function setTimestamp($timestamp = null) + { + $this->storage_timestamp = $timestamp ?? time(); + + return $this; + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::render() + */ + public function render(string $layout = null, array $context = []) + { + if (!$layout) { + $config = $this->getTemplateConfig(); + $layout = $config['object']['defaults']['layout'] ?? 'default'; + } + + $type = $this->getFlexType(); + + $grav = Grav::instance(); + + /** @var Debugger $debugger */ + $debugger = $grav['debugger']; + $debugger->startTimer('flex-object-' . ($debugKey = uniqid($type, false)), 'Render Object ' . $type . ' (' . $layout . ')'); + + $key = $this->getCacheKey(); + + // Disable caching if context isn't all scalars. + if ($key) { + foreach ($context as $value) { + if (!is_scalar($value)) { + $key = ''; + break; + } + } + } + + if ($key) { + // Create a new key which includes layout and context. + $key = md5($key . '.' . $layout . json_encode($context)); + $cache = $this->getCache('render'); + } else { + $cache = null; + } + + try { + $data = $cache ? $cache->get($key) : null; + + $block = $data ? HtmlBlock::fromArray($data) : null; + } catch (InvalidArgumentException $e) { + $debugger->addException($e); + + $block = null; + } catch (\InvalidArgumentException $e) { + $debugger->addException($e); + + $block = null; + } + + $checksum = $this->getCacheChecksum(); + if ($block && $checksum !== $block->getChecksum()) { + $block = null; + } + + if (!$block) { + $block = HtmlBlock::create($key ?: null); + $block->setChecksum($checksum); + if (!$cache) { + $block->disableCache(); + } + + $event = new Event([ + 'type' => 'flex', + 'directory' => $this->getFlexDirectory(), + 'object' => $this, + 'layout' => &$layout, + 'context' => &$context + ]); + $this->triggerEvent('onRender', $event); + + $output = $this->getTemplate($layout)->render( + [ + 'grav' => $grav, + 'config' => $grav['config'], + 'block' => $block, + 'directory' => $this->getFlexDirectory(), + 'object' => $this, + 'layout' => $layout + ] + $context + ); + + if ($debugger->enabled() && + !($grav['uri']->getContentType() === 'application/json' || $grav['uri']->extension() === 'json')) { + $name = $this->getKey() . ' (' . $type . ')'; + $output = "\n\n{$output}\n\n"; + } + + $block->setContent($output); + + try { + $cache && $block->isCached() && $cache->set($key, $block->toArray()); + } catch (InvalidArgumentException $e) { + $debugger->addException($e); + } + } + + $debugger->stopTimer('flex-object-' . $debugKey); + + return $block; + } + + /** + * @return array + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->getElements(); + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::prepareStorage() + */ + public function prepareStorage(): array + { + return ['__META' => $this->getMetaData()] + $this->getElements(); + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::update() + */ + public function update(array $data, array $files = []) + { + if ($data) { + // Get currently stored data. + $elements = $this->getElements(); + + // Store original version of the object. + if ($this->_original === null) { + $this->_original = $elements; + } + + $blueprint = $this->getBlueprint(); + + // Process updated data through the object filters. + $this->filterElements($data); + + // Merge existing object to the test data to be validated. + $test = $blueprint->mergeData($elements, $data); + + // Validate and filter elements and throw an error if any issues were found. + $blueprint->validate($test + ['storage_key' => $this->getStorageKey(), 'timestamp' => $this->getTimestamp()], ['xss_check' => false]); + $data = $blueprint->filter($data, true, true); + + // Finally update the object. + $flattenData = $blueprint->flattenData($data); + foreach ($flattenData as $key => $value) { + if ($value === null) { + $this->unsetNestedProperty($key); + } else { + $this->setNestedProperty($key, $value); + } + } + } + + if ($files && method_exists($this, 'setUpdatedMedia')) { + $this->setUpdatedMedia($files); + } + + return $this; + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::create() + */ + public function create(string $key = null) + { + if ($key) { + $this->setStorageKey($key); + } + + if ($this->exists()) { + throw new RuntimeException('Cannot create new object (Already exists)'); + } + + return $this->save(); + } + + /** + * @param string|null $key + * @return FlexObject|FlexObjectInterface + */ + public function createCopy(string $key = null) + { + $this->markAsCopy(); + + return $this->create($key); + } + + /** + * @param UserInterface|null $user + */ + public function check(UserInterface $user = null): void + { + // If user has been provided, check if the user has permissions to save this object. + if ($user && !$this->isAuthorized('save', null, $user)) { + throw new \RuntimeException('Forbidden', 403); + } + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::save() + */ + public function save() + { + $this->triggerEvent('onBeforeSave'); + + $storage = $this->getFlexDirectory()->getStorage(); + + $storageKey = $this->getStorageKey() ?: '@@' . spl_object_hash($this); + + $result = $storage->replaceRows([$storageKey => $this->prepareStorage()]); + + if (method_exists($this, 'clearMediaCache')) { + $this->clearMediaCache(); + } + + $value = reset($result); + $meta = $value['__META'] ?? null; + if ($meta) { + /** @phpstan-var class-string $indexClass */ + $indexClass = $this->getFlexDirectory()->getIndexClass(); + $indexClass::updateObjectMeta($meta, $value, $storage); + $this->_meta = $meta; + } + + if ($value) { + $storageKey = $meta['storage_key'] ?? (string)key($result); + if ($storageKey !== '') { + $this->setStorageKey($storageKey); + } + + $newKey = $meta['key'] ?? ($this->hasKey() ? $this->getKey() : null); + $this->setKey($newKey ?? $storageKey); + } + + // FIXME: For some reason locator caching isn't cleared for the file, investigate! + $locator = Grav::instance()['locator']; + $locator->clearCache(); + + if (method_exists($this, 'saveUpdatedMedia')) { + $this->saveUpdatedMedia(); + } + + try { + $this->getFlexDirectory()->reloadIndex(); + if (method_exists($this, 'clearMediaCache')) { + $this->clearMediaCache(); + } + } catch (Exception $e) { + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + $debugger->addException($e); + + // Caching failed, but we can ignore that for now. + } + + $this->triggerEvent('onAfterSave'); + + return $this; + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::delete() + */ + public function delete() + { + if (!$this->exists()) { + return $this; + } + + $this->triggerEvent('onBeforeDelete'); + + $this->getFlexDirectory()->getStorage()->deleteRows([$this->getStorageKey() => $this->prepareStorage()]); + + try { + $this->getFlexDirectory()->reloadIndex(); + if (method_exists($this, 'clearMediaCache')) { + $this->clearMediaCache(); + } + } catch (Exception $e) { + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + $debugger->addException($e); + + // Caching failed, but we can ignore that for now. + } + + $this->triggerEvent('onAfterDelete'); + + return $this; + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::getBlueprint() + */ + public function getBlueprint(string $name = '') + { + if (!isset($this->_blueprint[$name])) { + $blueprint = $this->doGetBlueprint($name); + $blueprint->setScope('object'); + $blueprint->setObject($this); + + $this->_blueprint[$name] = $blueprint->init(); + } + + return $this->_blueprint[$name]; + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::getForm() + */ + public function getForm(string $name = '', array $options = null) + { + $hash = $name . '-' . md5(json_encode($options, JSON_THROW_ON_ERROR)); + if (!isset($this->_forms[$hash])) { + $this->_forms[$hash] = $this->createFormObject($name, $options); + } + + return $this->_forms[$hash]; + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::getDefaultValue() + */ + public function getDefaultValue(string $name, string $separator = null) + { + $separator = $separator ?: '.'; + $path = explode($separator, $name); + $offset = array_shift($path); + + $current = $this->getDefaultValues(); + + if (!isset($current[$offset])) { + return null; + } + + $current = $current[$offset]; + + while ($path) { + $offset = array_shift($path); + + if ((is_array($current) || $current instanceof ArrayAccess) && isset($current[$offset])) { + $current = $current[$offset]; + } elseif (is_object($current) && isset($current->{$offset})) { + $current = $current->{$offset}; + } else { + return null; + } + }; + + return $current; + } + + /** + * @return array + */ + public function getDefaultValues(): array + { + return $this->getBlueprint()->getDefaults(); + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::getFormValue() + */ + public function getFormValue(string $name, $default = null, string $separator = null) + { + if ($name === 'storage_key') { + return $this->getStorageKey(); + } + if ($name === 'storage_timestamp') { + return $this->getTimestamp(); + } + + return $this->getNestedProperty($name, $default, $separator); + } + + /** + * @param FlexDirectory $directory + */ + public function setFlexDirectory(FlexDirectory $directory): void + { + $this->_flexDirectory = $directory; + } + + /** + * Returns a string representation of this object. + * + * @return string + */ + #[\ReturnTypeWillChange] + public function __toString() + { + return $this->getFlexKey(); + } + + /** + * @return array + */ + #[\ReturnTypeWillChange] + public function __debugInfo() + { + return [ + 'type:private' => $this->getFlexType(), + 'storage_key:protected' => $this->getStorageKey(), + 'storage_timestamp:protected' => $this->getTimestamp(), + 'key:private' => $this->getKey(), + 'elements:private' => $this->getElements(), + 'storage:private' => $this->getMetaData() + ]; + } + + /** + * Clone object. + */ + #[\ReturnTypeWillChange] + public function __clone() + { + // Allows future compatibility as parent::__clone() works. + } + + protected function markAsCopy(): void + { + $meta = $this->getMetaData(); + $meta['copy'] = true; + $this->_meta = $meta; + } + + /** + * @param string $name + * @return Blueprint + */ + protected function doGetBlueprint(string $name = ''): Blueprint + { + return $this->_flexDirectory->getBlueprint($name ? '.' . $name : $name); + } + + /** + * @param array $meta + */ + protected function setMetaData(array $meta): void + { + $this->_meta = $meta; + } + + /** + * @return array + */ + protected function doSerialize(): array + { + return [ + 'type' => $this->getFlexType(), + 'key' => $this->getKey(), + 'elements' => $this->getElements(), + 'storage' => $this->getMetaData() + ]; + } + + /** + * @param array $serialized + * @param FlexDirectory|null $directory + * @return void + */ + protected function doUnserialize(array $serialized, FlexDirectory $directory = null): void + { + $type = $serialized['type'] ?? 'unknown'; + + if (!isset($serialized['key'], $serialized['type'], $serialized['elements'])) { + throw new \InvalidArgumentException("Cannot unserialize '{$type}': Bad data"); + } + + if (null === $directory) { + $directory = $this->getFlexContainer()->getDirectory($type); + if (!$directory) { + throw new \InvalidArgumentException("Cannot unserialize Flex type '{$type}': Directory not found"); + } + } + + $this->setFlexDirectory($directory); + $this->setMetaData($serialized['storage']); + $this->setKey($serialized['key']); + $this->setElements($serialized['elements']); + } + + /** + * @return array + */ + protected function getTemplateConfig() + { + $config = $this->getFlexDirectory()->getConfig('site.templates', []); + $defaults = array_replace($config['defaults'] ?? [], $config['object']['defaults'] ?? []); + $config['object']['defaults'] = $defaults; + + return $config; + } + + /** + * @param string $layout + * @return array + */ + protected function getTemplatePaths(string $layout): array + { + $config = $this->getTemplateConfig(); + $type = $this->getFlexType(); + $defaults = $config['object']['defaults'] ?? []; + + $ext = $defaults['ext'] ?? '.html.twig'; + $types = array_unique(array_merge([$type], (array)($defaults['type'] ?? null))); + $paths = $config['object']['paths'] ?? [ + 'flex/{TYPE}/object/{LAYOUT}{EXT}', + 'flex-objects/layouts/{TYPE}/object/{LAYOUT}{EXT}' + ]; + $table = ['TYPE' => '%1$s', 'LAYOUT' => '%2$s', 'EXT' => '%3$s']; + + $lookups = []; + foreach ($paths as $path) { + $path = Utils::simpleTemplate($path, $table); + foreach ($types as $type) { + $lookups[] = sprintf($path, $type, $layout, $ext); + } + } + + return array_unique($lookups); + } + + /** + * Filter data coming to constructor or $this->update() request. + * + * NOTE: The incoming data can be an arbitrary array so do not assume anything from its content. + * + * @param array $elements + */ + protected function filterElements(array &$elements): void + { + if (isset($elements['storage_key'])) { + $elements['storage_key'] = trim($elements['storage_key']); + } + if (isset($elements['storage_timestamp'])) { + $elements['storage_timestamp'] = (int)$elements['storage_timestamp']; + } + + unset($elements['_post_entries_save']); + } + + /** + * This methods allows you to override form objects in child classes. + * + * @param string $name Form name + * @param array|null $options Form optiosn + * @return FlexFormInterface + */ + protected function createFormObject(string $name, array $options = null) + { + return new FlexForm($name, $this, $options); + } + + /** + * @param string $action + * @return string + */ + protected function getAuthorizeAction(string $action): string + { + // Handle special action save, which can mean either update or create. + if ($action === 'save') { + $action = $this->exists() ? 'update' : 'create'; + } + + return $action; + } + + /** + * Method to reset blueprints if the type changes. + * + * @return void + * @since 1.7.18 + */ + protected function resetBlueprints(): void + { + $this->_blueprint = []; + } + + // DEPRECATED METHODS + + /** + * @param bool $prefix + * @return string + * @deprecated 1.6 Use `->getFlexType()` instead. + */ + public function getType($prefix = false) + { + user_error(__METHOD__ . '() is deprecated since Grav 1.6, use ->getFlexType() method instead', E_USER_DEPRECATED); + + $type = $prefix ? $this->getTypePrefix() : ''; + + return $type . $this->getFlexType(); + } + + /** + * @param string $name + * @param mixed|null $default + * @param string|null $separator + * @return mixed + * + * @deprecated 1.6 Use ->getFormValue() method instead. + */ + public function value($name, $default = null, $separator = null) + { + user_error(__METHOD__ . '() is deprecated since Grav 1.6, use ->getFormValue() method instead', E_USER_DEPRECATED); + + return $this->getFormValue($name, $default, $separator); + } + + /** + * @param string $name + * @param object|null $event + * @return $this + * @deprecated 1.7 Moved to \Grav\Common\Flex\Traits\FlexObjectTrait + */ + public function triggerEvent(string $name, $event = null) + { + user_error(__METHOD__ . '() is deprecated since Grav 1.7, moved to \Grav\Common\Flex\Traits\FlexObjectTrait', E_USER_DEPRECATED); + + if (null === $event) { + $event = new Event([ + 'type' => 'flex', + 'directory' => $this->getFlexDirectory(), + 'object' => $this + ]); + } + if (strpos($name, 'onFlexObject') !== 0 && strpos($name, 'on') === 0) { + $name = 'onFlexObject' . substr($name, 2); + } + + $grav = Grav::instance(); + if ($event instanceof Event) { + $grav->fireEvent($name, $event); + } else { + $grav->dispatchEvent($event); + } + + return $this; + } + + /** + * @param array $storage + * @deprecated 1.7 Use `->setMetaData()` instead. + */ + protected function setStorage(array $storage): void + { + user_error(__METHOD__ . '() is deprecated since Grav 1.7, use ->setMetaData() method instead', E_USER_DEPRECATED); + + $this->setMetaData($storage); + } + + /** + * @return array + * @deprecated 1.7 Use `->getMetaData()` instead. + */ + protected function getStorage(): array + { + user_error(__METHOD__ . '() is deprecated since Grav 1.7, use ->getMetaData() method instead', E_USER_DEPRECATED); + + return $this->getMetaData(); + } + + /** + * @param string $layout + * @return Template|TemplateWrapper + * @throws LoaderError + * @throws SyntaxError + * @deprecated 1.7 Moved to \Grav\Common\Flex\Traits\GravTrait + */ + protected function getTemplate($layout) + { + user_error(__METHOD__ . '() is deprecated since Grav 1.7, moved to \Grav\Common\Flex\Traits\GravTrait', E_USER_DEPRECATED); + + $grav = Grav::instance(); + + /** @var Twig $twig */ + $twig = $grav['twig']; + + try { + return $twig->twig()->resolveTemplate($this->getTemplatePaths($layout)); + } catch (LoaderError $e) { + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + $debugger->addException($e); + + return $twig->twig()->resolveTemplate(['flex/404.html.twig']); + } + } + + /** + * @return Flex + * @deprecated 1.7 Moved to \Grav\Common\Flex\Traits\GravTrait + */ + protected function getFlexContainer(): Flex + { + user_error(__METHOD__ . '() is deprecated since Grav 1.7, moved to \Grav\Common\Flex\Traits\GravTrait', E_USER_DEPRECATED); + + /** @var Flex $flex */ + $flex = Grav::instance()['flex']; + + return $flex; + } + + /** + * @return UserInterface|null + * @deprecated 1.7 Moved to \Grav\Common\Flex\Traits\GravTrait + */ + protected function getActiveUser(): ?UserInterface + { + user_error(__METHOD__ . '() is deprecated since Grav 1.7, moved to \Grav\Common\Flex\Traits\GravTrait', E_USER_DEPRECATED); + + /** @var UserInterface|null $user */ + $user = Grav::instance()['user'] ?? null; + + return $user; + } + + /** + * @return string + * @deprecated 1.7 Moved to \Grav\Common\Flex\Traits\GravTrait + */ + protected function getAuthorizeScope(): string + { + user_error(__METHOD__ . '() is deprecated since Grav 1.7, moved to \Grav\Common\Flex\Traits\GravTrait', E_USER_DEPRECATED); + + return isset(Grav::instance()['admin']) ? 'admin' : 'site'; + } +} diff --git a/system/src/Grav/Framework/Flex/Interfaces/FlexAuthorizeInterface.php b/system/src/Grav/Framework/Flex/Interfaces/FlexAuthorizeInterface.php new file mode 100644 index 0000000..2fd6c7e --- /dev/null +++ b/system/src/Grav/Framework/Flex/Interfaces/FlexAuthorizeInterface.php @@ -0,0 +1,33 @@ + + */ +interface FlexCollectionInterface extends FlexCommonInterface, ObjectCollectionInterface, NestedObjectInterface +{ + /** + * Creates a Flex Collection from an array. + * + * @used-by FlexDirectory::createCollection() Official method to create a Flex Collection. + * + * @param FlexObjectInterface[] $entries Associated array of Flex Objects to be included in the collection. + * @param FlexDirectory $directory Flex Directory where all the objects belong into. + * @param string|null $keyField Key field used to index the collection. + * @return static Returns a new Flex Collection. + */ + public static function createFromArray(array $entries, FlexDirectory $directory, string $keyField = null); + + /** + * Creates a new Flex Collection. + * + * @used-by FlexDirectory::createCollection() Official method to create Flex Collection. + * + * @param FlexObjectInterface[] $entries Associated array of Flex Objects to be included in the collection. + * @param FlexDirectory|null $directory Flex Directory where all the objects belong into. + * @throws InvalidArgumentException + */ + public function __construct(array $entries = [], FlexDirectory $directory = null); + + /** + * Search a string from the collection. + * + * @param string $search Search string. + * @param string|string[]|null $properties Properties to search for, defaults to configured properties. + * @param array|null $options Search options, defaults to configured options. + * @return FlexCollectionInterface Returns a Flex Collection with only matching objects. + * @phpstan-return static + * @api + */ + public function search(string $search, $properties = null, array $options = null); + + /** + * Sort the collection. + * + * @param array $orderings Pair of [property => 'ASC'|'DESC', ...]. + * + * @return FlexCollectionInterface Returns a sorted version from the collection. + * @phpstan-return static + */ + public function sort(array $orderings); + + /** + * Filter collection by filter array with keys and values. + * + * @param array $filters + * @return FlexCollectionInterface + * @phpstan-return static + */ + public function filterBy(array $filters); + + /** + * Get timestamps from all the objects in the collection. + * + * This method can be used for example in caching. + * + * @return int[] Returns [key => timestamp, ...] pairs. + */ + public function getTimestamps(): array; + + /** + * Get storage keys from all the objects in the collection. + * + * @see FlexDirectory::getObject() If you want to get Flex Object from the Flex Directory. + * + * @return string[] Returns [key => storage_key, ...] pairs. + */ + public function getStorageKeys(): array; + + /** + * Get Flex keys from all the objects in the collection. + * + * @see Flex::getObjects() If you want to get list of Flex Objects from any Flex Directory. + * + * @return string[] Returns[key => flex_key, ...] pairs. + */ + public function getFlexKeys(): array; + + /** + * Return new collection with a different key. + * + * @param string|null $keyField Switch key field of the collection. + * @return FlexCollectionInterface Returns a new Flex Collection with new key field. + * @phpstan-return static + * @api + */ + public function withKeyField(string $keyField = null); + + /** + * Get Flex Index from the Flex Collection. + * + * @return FlexIndexInterface Returns a Flex Index from the current collection. + * @phpstan-return FlexIndexInterface + */ + public function getIndex(); + + /** + * Load all the objects into memory, + * + * @return FlexCollectionInterface + * @phpstan-return static + */ + public function getCollection(); + + /** + * Get metadata associated to the object + * + * @param string $key Key. + * @return array + */ + public function getMetaData($key): array; +} diff --git a/system/src/Grav/Framework/Flex/Interfaces/FlexCommonInterface.php b/system/src/Grav/Framework/Flex/Interfaces/FlexCommonInterface.php new file mode 100644 index 0000000..181bf2a --- /dev/null +++ b/system/src/Grav/Framework/Flex/Interfaces/FlexCommonInterface.php @@ -0,0 +1,79 @@ +getBlueprint() or $object->getForm()->getBlueprint() instead. + * + * @param string $type + * @param string $context + * @return Blueprint + */ + public function getBlueprint(string $type = '', string $context = ''); + + /** + * @param string $view + * @return string + */ + public function getBlueprintFile(string $view = ''): string; + + /** + * Get collection. In the site this will be filtered by the default filters (published etc). + * + * Use $directory->getIndex() if you want unfiltered collection. + * + * @param array|null $keys Array of keys. + * @param string|null $keyField Field to be used as the key. + * @return FlexCollectionInterface + * @phpstan-return FlexCollectionInterface + */ + public function getCollection(array $keys = null, string $keyField = null): FlexCollectionInterface; + + /** + * Get the full collection of all stored objects. + * + * Use $directory->getCollection() if you want a filtered collection. + * + * @param array|null $keys Array of keys. + * @param string|null $keyField Field to be used as the key. + * @return FlexIndexInterface + * @phpstan-return FlexIndexInterface + */ + public function getIndex(array $keys = null, string $keyField = null): FlexIndexInterface; + + /** + * Returns an object if it exists. If no arguments are passed (or both of them are null), method creates a new empty object. + * + * Note: It is not safe to use the object without checking if the user can access it. + * + * @param string|null $key + * @param string|null $keyField Field to be used as the key. + * @return FlexObjectInterface|null + */ + public function getObject($key = null, string $keyField = null): ?FlexObjectInterface; + + /** + * @param string|null $namespace + * @return CacheInterface + */ + public function getCache(string $namespace = null); + + /** + * @return $this + */ + public function clearCache(); + + /** + * @param string|null $key + * @return string|null + */ + public function getStorageFolder(string $key = null): ?string; + + /** + * @param string|null $key + * @return string|null + */ + public function getMediaFolder(string $key = null): ?string; + + /** + * @return FlexStorageInterface + */ + public function getStorage(): FlexStorageInterface; + + /** + * @param array $data + * @param string $key + * @param bool $validate + * @return FlexObjectInterface + */ + public function createObject(array $data, string $key = '', bool $validate = false): FlexObjectInterface; + + /** + * @param array $entries + * @param string|null $keyField + * @return FlexCollectionInterface + * @phpstan-return FlexCollectionInterface + */ + public function createCollection(array $entries, string $keyField = null): FlexCollectionInterface; + + /** + * @param array $entries + * @param string|null $keyField + * @return FlexIndexInterface + * @phpstan-return FlexIndexInterface + */ + public function createIndex(array $entries, string $keyField = null): FlexIndexInterface; + + /** + * @return string + */ + public function getObjectClass(): string; + + /** + * @return string + */ + public function getCollectionClass(): string; + + /** + * @return string + */ + public function getIndexClass(): string; + + /** + * @param array $entries + * @param string|null $keyField + * @return FlexCollectionInterface + * @phpstan-return FlexCollectionInterface + */ + public function loadCollection(array $entries, string $keyField = null): FlexCollectionInterface; + + /** + * @param array $entries + * @return FlexObjectInterface[] + * @internal + */ + public function loadObjects(array $entries): array; + + /** + * @return void + */ + public function reloadIndex(): void; + + /** + * @param string $scope + * @param string $action + * @return string + */ + public function getAuthorizeRule(string $scope, string $action): string; +} diff --git a/system/src/Grav/Framework/Flex/Interfaces/FlexFormInterface.php b/system/src/Grav/Framework/Flex/Interfaces/FlexFormInterface.php new file mode 100644 index 0000000..c2e8453 --- /dev/null +++ b/system/src/Grav/Framework/Flex/Interfaces/FlexFormInterface.php @@ -0,0 +1,51 @@ + + */ +interface FlexIndexInterface extends FlexCollectionInterface +{ + /** + * Helper method to create Flex Index. + * + * @used-by FlexDirectory::getIndex() Official method to get Index from a Flex Directory. + * + * @param FlexDirectory $directory Flex directory. + * @return static Returns a new Flex Index. + */ + public static function createFromStorage(FlexDirectory $directory); + + /** + * Method to load index from the object storage, usually filesystem. + * + * @used-by FlexDirectory::getIndex() Official method to get Index from a Flex Directory. + * + * @param FlexStorageInterface $storage Flex Storage associated to the directory. + * @return array Returns a list of existing objects [storage_key => [storage_key => xxx, storage_timestamp => 123456, ...]] + */ + public static function loadEntriesFromStorage(FlexStorageInterface $storage): array; + + /** + * Return new collection with a different key. + * + * @param string|null $keyField Switch key field of the collection. + * @return static Returns a new Flex Collection with new key field. + * @phpstan-return static + * @api + */ + public function withKeyField(string $keyField = null); + + /** + * @param string|null $indexKey + * @return array + */ + public function getIndexMap(string $indexKey = null); +} diff --git a/system/src/Grav/Framework/Flex/Interfaces/FlexInterface.php b/system/src/Grav/Framework/Flex/Interfaces/FlexInterface.php new file mode 100644 index 0000000..4e38163 --- /dev/null +++ b/system/src/Grav/Framework/Flex/Interfaces/FlexInterface.php @@ -0,0 +1,100 @@ + + */ + public function getDirectories(array $types = null, bool $keepMissing = false): array; + + /** + * @param string $type + * @return FlexDirectory|null + */ + public function getDirectory(string $type): ?FlexDirectory; + + /** + * @param string $type + * @param array|null $keys + * @param string|null $keyField + * @return FlexCollectionInterface|null + * @phpstan-return FlexCollectionInterface|null + */ + public function getCollection(string $type, array $keys = null, string $keyField = null): ?FlexCollectionInterface; + + /** + * @param array $keys + * @param array $options In addition to the options in getObjects(), following options can be passed: + * collection_class: Class to be used to create the collection. Defaults to ObjectCollection. + * @return FlexCollectionInterface + * @throws RuntimeException + * @phpstan-return FlexCollectionInterface + */ + public function getMixedCollection(array $keys, array $options = []): FlexCollectionInterface; + + /** + * @param array $keys + * @param array $options Following optional options can be passed: + * types: List of allowed types. + * type: Allowed type if types isn't defined, otherwise acts as default_type. + * default_type: Set default type for objects given without type (only used if key_field isn't set). + * keep_missing: Set to true if you want to return missing objects as null. + * key_field: Key field which is used to match the objects. + * @return array + */ + public function getObjects(array $keys, array $options = []): array; + + /** + * @param string $key + * @param string|null $type + * @param string|null $keyField + * @return FlexObjectInterface|null + */ + public function getObject(string $key, string $type = null, string $keyField = null): ?FlexObjectInterface; + + /** + * @return int + */ + public function count(): int; +} diff --git a/system/src/Grav/Framework/Flex/Interfaces/FlexObjectFormInterface.php b/system/src/Grav/Framework/Flex/Interfaces/FlexObjectFormInterface.php new file mode 100644 index 0000000..41ff68e --- /dev/null +++ b/system/src/Grav/Framework/Flex/Interfaces/FlexObjectFormInterface.php @@ -0,0 +1,27 @@ + + * @used-by \Grav\Framework\Flex\FlexObject + * @since 1.6 + */ +interface FlexObjectInterface extends FlexCommonInterface, NestedObjectInterface, ArrayAccess +{ + /** + * Construct a new Flex Object instance. + * + * @used-by FlexDirectory::createObject() Method to create Flex Object. + * + * @param array $elements Array of object properties. + * @param string $key Identifier key for the new object. + * @param FlexDirectory $directory Flex Directory the object belongs into. + * @param bool $validate True if the object should be validated against blueprint. + * @throws InvalidArgumentException + */ + public function __construct(array $elements, $key, FlexDirectory $directory, bool $validate = false); + + /** + * Search a string from the object, returns weight between 0 and 1. + * + * Note: If you override this function, make sure you return value in range 0...1! + * + * @used-by FlexCollectionInterface::search() If you want to search a string from a Flex Collection. + * + * @param string $search Search string. + * @param string|string[]|null $properties Properties to search for, defaults to configured properties. + * @param array|null $options Search options, defaults to configured options. + * @return float Returns a weight between 0 and 1. + * @api + */ + public function search(string $search, $properties = null, array $options = null): float; + + /** + * Returns true if object has a key. + * + * @return bool + */ + public function hasKey(); + + /** + * Get a unique key for the object. + * + * Flex Keys can be used without knowing the Directory the Object belongs into. + * + * @see Flex::getObject() If you want to get Flex Object from any Flex Directory. + * @see Flex::getObjects() If you want to get list of Flex Objects from any Flex Directory. + * + * NOTE: Please do not override the method! + * + * @return string Returns Flex Key of the object. + * @api + */ + public function getFlexKey(): string; + + /** + * Get an unique storage key (within the directory) which is used for figuring out the filename or database id. + * + * @see FlexDirectory::getObject() If you want to get Flex Object from the Flex Directory. + * @see FlexDirectory::getCollection() If you want to get Flex Collection with selected keys from the Flex Directory. + * + * @return string Returns storage key of the Object. + * @api + */ + public function getStorageKey(): string; + + /** + * Get index data associated to the object. + * + * @return array Returns metadata of the object. + */ + public function getMetaData(): array; + + /** + * Returns true if the object exists in the storage. + * + * @return bool Returns `true` if the object exists, `false` otherwise. + * @api + */ + public function exists(): bool; + + /** + * Prepare object for saving into the storage. + * + * @return array Returns an array of object properties containing only scalars and arrays. + */ + public function prepareStorage(): array; + + /** + * Updates object in the memory. + * + * @see FlexObjectInterface::save() You need to save the object after calling this method. + * + * @param array $data Data containing updated properties with their values. To unset a value, use `null`. + * @param array|UploadedFileInterface[] $files List of uploaded files to be saved within the object. + * @return static + * @throws RuntimeException + * @api + */ + public function update(array $data, array $files = []); + + /** + * Create new object into the storage. + * + * @see FlexDirectory::createObject() If you want to create a new object instance. + * @see FlexObjectInterface::update() If you want to update properties of the object. + * + * @param string|null $key Optional new key. If key isn't given, random key will be associated to the object. + * @return static + * @throws RuntimeException if object already exists. + * @api + */ + public function create(string $key = null); + + /** + * Save object into the storage. + * + * @see FlexObjectInterface::update() If you want to update properties of the object. + * + * @return static + * @api + */ + public function save(); + + /** + * Delete object from the storage. + * + * @return static + * @api + */ + public function delete(); + + /** + * Returns the blueprint of the object. + * + * @see FlexObjectInterface::getForm() + * @used-by FlexForm::getBlueprint() + * + * @param string $name Name of the Blueprint form. Used to create customized forms for different use cases. + * @return Blueprint Returns a Blueprint. + */ + public function getBlueprint(string $name = ''); + + /** + * Returns a form instance for the object. + * + * @param string $name Name of the form. Can be used to create customized forms for different use cases. + * @param array|null $options Options can be used to further customize the form. + * @return FlexFormInterface Returns a Form. + * @api + */ + public function getForm(string $name = '', array $options = null); + + /** + * Returns default value suitable to be used in a form for the given property. + * + * @see FlexObjectInterface::getForm() + * + * @param string $name Property name. + * @param string|null $separator Optional nested property separator. + * @return mixed|null Returns default value of the field, null if there is no default value. + */ + public function getDefaultValue(string $name, string $separator = null); + + /** + * Returns default values suitable to be used in a form for the given property. + * + * @see FlexObjectInterface::getForm() + * + * @return array Returns default values. + */ + public function getDefaultValues(): array; + + /** + * Returns raw value suitable to be used in a form for the given property. + * + * @see FlexObjectInterface::getForm() + * + * @param string $name Property name. + * @param mixed $default Default value. + * @param string|null $separator Optional nested property separator. + * @return mixed Returns value of the field. + */ + public function getFormValue(string $name, $default = null, string $separator = null); +} diff --git a/system/src/Grav/Framework/Flex/Interfaces/FlexStorageInterface.php b/system/src/Grav/Framework/Flex/Interfaces/FlexStorageInterface.php new file mode 100644 index 0000000..7dca589 --- /dev/null +++ b/system/src/Grav/Framework/Flex/Interfaces/FlexStorageInterface.php @@ -0,0 +1,138 @@ + [storage_key => key, storage_timestamp => timestamp], ...]`. + */ + public function getExistingKeys(): array; + + /** + * Check if the key exists in the storage. + * + * @param string $key Storage key of an object. + * @return bool Returns `true` if the key exists in the storage, `false` otherwise. + */ + public function hasKey(string $key): bool; + + /** + * Check if the key exists in the storage. + * + * @param string[] $keys Storage key of an object. + * @return bool[] Returns keys with `true` if the key exists in the storage, `false` otherwise. + */ + public function hasKeys(array $keys): array; + + /** + * Create new rows into the storage. + * + * New keys will be assigned when the objects are created. + * + * @param array $rows List of rows as `[row, ...]`. + * @return array Returns created rows as `[key => row, ...] pairs. + */ + public function createRows(array $rows): array; + + /** + * Read rows from the storage. + * + * If you pass object or array as value, that value will be used to save I/O. + * + * @param array $rows Array of `[key => row, ...]` pairs. + * @param array|null $fetched Optional reference to store only fetched items. + * @return array Returns rows. Note that non-existing rows will have `null` as their value. + */ + public function readRows(array $rows, array &$fetched = null): array; + + /** + * Update existing rows in the storage. + * + * @param array $rows Array of `[key => row, ...]` pairs. + * @return array Returns updated rows. Note that non-existing rows will not be saved and have `null` as their value. + */ + public function updateRows(array $rows): array; + + /** + * Delete rows from the storage. + * + * @param array $rows Array of `[key => row, ...]` pairs. + * @return array Returns deleted rows. Note that non-existing rows have `null` as their value. + */ + public function deleteRows(array $rows): array; + + /** + * Replace rows regardless if they exist or not. + * + * All rows should have a specified key for replace to work properly. + * + * @param array $rows Array of `[key => row, ...]` pairs. + * @return array Returns both created and updated rows. + */ + public function replaceRows(array $rows): array; + + /** + * @param string $src + * @param string $dst + * @return bool + */ + public function copyRow(string $src, string $dst): bool; + + /** + * @param string $src + * @param string $dst + * @return bool + */ + public function renameRow(string $src, string $dst): bool; + + /** + * Get filesystem path for the collection or object storage. + * + * @param string|null $key Optional storage key. + * @return string|null Path in the filesystem. Can be URI or null if storage is not filesystem based. + */ + public function getStoragePath(string $key = null): ?string; + + /** + * Get filesystem path for the collection or object media. + * + * @param string|null $key Optional storage key. + * @return string|null Path in the filesystem. Can be URI or null if media isn't supported. + */ + public function getMediaPath(string $key = null): ?string; +} diff --git a/system/src/Grav/Framework/Flex/Interfaces/FlexTranslateInterface.php b/system/src/Grav/Framework/Flex/Interfaces/FlexTranslateInterface.php new file mode 100644 index 0000000..4e7e8f7 --- /dev/null +++ b/system/src/Grav/Framework/Flex/Interfaces/FlexTranslateInterface.php @@ -0,0 +1,51 @@ + + */ +class FlexPageCollection extends FlexCollection +{ + /** + * @return array + */ + public static function getCachedMethods(): array + { + return [ + // Collection filtering + 'withPublished' => true, + 'withVisible' => true, + 'withRoutable' => true, + + 'isFirst' => true, + 'isLast' => true, + + // Find objects + 'prevSibling' => false, + 'nextSibling' => false, + 'adjacentSibling' => false, + 'currentPosition' => true, + + 'getNextOrder' => false, + ] + parent::getCachedMethods(); + } + + /** + * @param bool $bool + * @return static + * @phpstan-return static + */ + public function withPublished(bool $bool = true) + { + /** @var string[] $list */ + $list = array_keys(array_filter($this->call('isPublished', [$bool]))); + + /** @phpstan-var static */ + return $this->select($list); + } + + /** + * @param bool $bool + * @return static + * @phpstan-return static + */ + public function withVisible(bool $bool = true) + { + /** @var string[] $list */ + $list = array_keys(array_filter($this->call('isVisible', [$bool]))); + + /** @phpstan-var static */ + return $this->select($list); + } + + /** + * @param bool $bool + * @return static + * @phpstan-return static + */ + public function withRoutable(bool $bool = true) + { + /** @var string[] $list */ + $list = array_keys(array_filter($this->call('isRoutable', [$bool]))); + + /** @phpstan-var static */ + return $this->select($list); + } + + /** + * Check to see if this item is the first in the collection. + * + * @param string $path + * @return bool True if item is first. + */ + public function isFirst($path): bool + { + $keys = $this->getKeys(); + $first = reset($keys); + + return $path === $first; + } + + /** + * Check to see if this item is the last in the collection. + * + * @param string $path + * @return bool True if item is last. + */ + public function isLast($path): bool + { + $keys = $this->getKeys(); + $last = end($keys); + + return $path === $last; + } + + /** + * Gets the previous sibling based on current position. + * + * @param string $path + * @return PageInterface|false The previous item. + * @phpstan-return T|false + */ + public function prevSibling($path) + { + return $this->adjacentSibling($path, -1); + } + + /** + * Gets the next sibling based on current position. + * + * @param string $path + * @return PageInterface|false The next item. + * @phpstan-return T|false + */ + public function nextSibling($path) + { + return $this->adjacentSibling($path, 1); + } + + /** + * Returns the adjacent sibling based on a direction. + * + * @param string $path + * @param int $direction either -1 or +1 + * @return PageInterface|false The sibling item. + * @phpstan-return T|false + */ + public function adjacentSibling($path, $direction = 1) + { + $keys = $this->getKeys(); + $direction = (int)$direction; + $pos = array_search($path, $keys, true); + + if (is_int($pos)) { + $pos += $direction; + if (isset($keys[$pos])) { + return $this[$keys[$pos]]; + } + } + + return false; + } + + /** + * Returns the item in the current position. + * + * @param string $path the path the item + * @return int|null The index of the current page, null if not found. + */ + public function currentPosition($path): ?int + { + $pos = array_search($path, $this->getKeys(), true); + + return is_int($pos) ? $pos : null; + } + + /** + * @return string + */ + public function getNextOrder() + { + $directory = $this->getFlexDirectory(); + + $collection = $directory->getIndex(); + $keys = $collection->getStorageKeys(); + + // Assign next free order. + $last = null; + $order = 0; + foreach ($keys as $folder => $key) { + preg_match(FlexPageIndex::ORDER_PREFIX_REGEX, $folder, $test); + $test = $test[0] ?? null; + if ($test && $test > $order) { + $order = $test; + $last = $key; + } + } + + /** @var FlexPageObject|null $last */ + $last = $collection[$last]; + + return sprintf('%d.', $last ? $last->getFormValue('order') + 1 : 1); + } +} diff --git a/system/src/Grav/Framework/Flex/Pages/FlexPageIndex.php b/system/src/Grav/Framework/Flex/Pages/FlexPageIndex.php new file mode 100644 index 0000000..3ca6d04 --- /dev/null +++ b/system/src/Grav/Framework/Flex/Pages/FlexPageIndex.php @@ -0,0 +1,48 @@ + + */ +class FlexPageIndex extends FlexIndex +{ + public const ORDER_PREFIX_REGEX = '/^\d+\./u'; + + /** + * @param string $route + * @return string + * @internal + */ + public static function normalizeRoute(string $route) + { + static $case_insensitive; + + if (null === $case_insensitive) { + $case_insensitive = Grav::instance()['config']->get('system.force_lowercase_urls', false); + } + + return $case_insensitive ? mb_strtolower($route) : $route; + } +} diff --git a/system/src/Grav/Framework/Flex/Pages/FlexPageObject.php b/system/src/Grav/Framework/Flex/Pages/FlexPageObject.php new file mode 100644 index 0000000..54b295b --- /dev/null +++ b/system/src/Grav/Framework/Flex/Pages/FlexPageObject.php @@ -0,0 +1,496 @@ +header)) { + $this->header = clone($this->header); + } + } + + /** + * @return array + */ + public static function getCachedMethods(): array + { + return [ + // Page Content Interface + 'header' => false, + 'summary' => true, + 'content' => true, + 'value' => false, + 'media' => false, + 'title' => true, + 'menu' => true, + 'visible' => true, + 'published' => true, + 'publishDate' => true, + 'unpublishDate' => true, + 'process' => true, + 'slug' => true, + 'order' => true, + 'id' => true, + 'modified' => true, + 'lastModified' => true, + 'folder' => true, + 'date' => true, + 'dateformat' => true, + 'taxonomy' => true, + 'shouldProcess' => true, + 'isPage' => true, + 'isDir' => true, + 'folderExists' => true, + + // Page + 'isPublished' => true, + 'isOrdered' => true, + 'isVisible' => true, + 'isRoutable' => true, + 'getCreated_Timestamp' => true, + 'getPublish_Timestamp' => true, + 'getUnpublish_Timestamp' => true, + 'getUpdated_Timestamp' => true, + ] + parent::getCachedMethods(); + } + + /** + * @param bool $test + * @return bool + */ + public function isPublished(bool $test = true): bool + { + $time = time(); + $start = $this->getPublish_Timestamp(); + $stop = $this->getUnpublish_Timestamp(); + + return $this->published() && $start <= $time && (!$stop || $time <= $stop) === $test; + } + + /** + * @param bool $test + * @return bool + */ + public function isOrdered(bool $test = true): bool + { + return ($this->order() !== false) === $test; + } + + /** + * @param bool $test + * @return bool + */ + public function isVisible(bool $test = true): bool + { + return $this->visible() === $test; + } + + /** + * @param bool $test + * @return bool + */ + public function isRoutable(bool $test = true): bool + { + return $this->routable() === $test; + } + + /** + * @return int + */ + public function getCreated_Timestamp(): int + { + return $this->getFieldTimestamp('created_date') ?? 0; + } + + /** + * @return int + */ + public function getPublish_Timestamp(): int + { + return $this->getFieldTimestamp('publish_date') ?? $this->getCreated_Timestamp(); + } + + /** + * @return int|null + */ + public function getUnpublish_Timestamp(): ?int + { + return $this->getFieldTimestamp('unpublish_date'); + } + + /** + * @return int + */ + public function getUpdated_Timestamp(): int + { + return $this->getFieldTimestamp('updated_date') ?? $this->getPublish_Timestamp(); + } + + /** + * @inheritdoc + */ + public function getFormValue(string $name, $default = null, string $separator = null) + { + $test = new stdClass(); + + $value = $this->pageContentValue($name, $test); + if ($value !== $test) { + return $value; + } + + switch ($name) { + case 'name': + return $this->getProperty('template'); + case 'route': + return $this->hasKey() ? '/' . $this->getKey() : null; + case 'header.permissions.groups': + $encoded = json_encode($this->getPermissions()); + if ($encoded === false) { + throw new RuntimeException('json_encode(): failed to encode group permissions'); + } + + return json_decode($encoded, true); + } + + return parent::getFormValue($name, $default, $separator); + } + + /** + * Get master storage key. + * + * @return string + * @see FlexObjectInterface::getStorageKey() + */ + public function getMasterKey(): string + { + $key = (string)($this->storage_key ?? $this->getMetaData()['storage_key'] ?? null); + if (($pos = strpos($key, '|')) !== false) { + $key = substr($key, 0, $pos); + } + + return $key; + } + + /** + * {@inheritdoc} + * @see FlexObjectInterface::getCacheKey() + */ + public function getCacheKey(): string + { + return $this->hasKey() ? $this->getTypePrefix() . $this->getFlexType() . '.' . $this->getKey() . '.' . $this->getLanguage() : ''; + } + + /** + * @param string|null $key + * @return FlexObjectInterface + */ + public function createCopy(string $key = null) + { + $this->copy(); + + return parent::createCopy($key); + } + + /** + * @param array|bool $reorder + * @return FlexObject|FlexObjectInterface + */ + public function save($reorder = true) + { + return parent::save(); + } + + /** + * Gets the Page Unmodified (original) version of the page. + * + * Assumes that object has been cloned before modifying it. + * + * @return FlexPageObject|null The original version of the page. + */ + public function getOriginal() + { + return $this->_originalObject; + } + + /** + * Store the Page Unmodified (original) version of the page. + * + * Can be called multiple times, only the first call matters. + * + * @return void + */ + public function storeOriginal(): void + { + if (null === $this->_originalObject) { + $this->_originalObject = clone $this; + } + } + + /** + * Get display order for the associated media. + * + * @return array + */ + public function getMediaOrder(): array + { + $order = $this->getNestedProperty('header.media_order'); + + if (is_array($order)) { + return $order; + } + + if (!$order) { + return []; + } + + return array_map('trim', explode(',', $order)); + } + + // Overrides for header properties. + + /** + * Common logic to load header properties. + * + * @param string $property + * @param mixed $var + * @param callable $filter + * @return mixed|null + */ + protected function loadHeaderProperty(string $property, $var, callable $filter) + { + // We have to use parent methods in order to avoid loops. + $value = null === $var ? parent::getProperty($property) : null; + if (null === $value) { + $value = $filter($var ?? $this->getProperty('header')->get($property)); + + parent::setProperty($property, $value); + if ($this->doHasProperty($property)) { + $value = parent::getProperty($property); + } + } + + return $value; + } + + /** + * Common logic to load header properties. + * + * @param string $property + * @param mixed $var + * @param callable $filter + * @return mixed|null + */ + protected function loadProperty(string $property, $var, callable $filter) + { + // We have to use parent methods in order to avoid loops. + $value = null === $var ? parent::getProperty($property) : null; + if (null === $value) { + $value = $filter($var); + + parent::setProperty($property, $value); + if ($this->doHasProperty($property)) { + $value = parent::getProperty($property); + } + } + + return $value; + } + + /** + * @param string $property + * @param mixed $default + * @return mixed + */ + public function getProperty($property, $default = null) + { + $method = static::$headerProperties[$property] ?? static::$calculatedProperties[$property] ?? null; + if ($method && method_exists($this, $method)) { + return $this->{$method}(); + } + + return parent::getProperty($property, $default); + } + + /** + * @param string $property + * @param mixed $value + * @return $this + */ + public function setProperty($property, $value) + { + $method = static::$headerProperties[$property] ?? static::$calculatedProperties[$property] ?? null; + if ($method && method_exists($this, $method)) { + $this->{$method}($value); + + return $this; + } + + parent::setProperty($property, $value); + + return $this; + } + + /** + * @param string $property + * @param mixed $value + * @param string|null $separator + * @return $this + */ + public function setNestedProperty($property, $value, $separator = null) + { + $separator = $separator ?: '.'; + if (strpos($property, 'header' . $separator) === 0) { + $this->getProperty('header')->set(str_replace('header' . $separator, '', $property), $value, $separator); + + return $this; + } + + parent::setNestedProperty($property, $value, $separator); + + return $this; + } + + /** + * @param string $property + * @param string|null $separator + * @return $this + */ + public function unsetNestedProperty($property, $separator = null) + { + $separator = $separator ?: '.'; + if (strpos($property, 'header' . $separator) === 0) { + $this->getProperty('header')->undef(str_replace('header' . $separator, '', $property), $separator); + + return $this; + } + + parent::unsetNestedProperty($property, $separator); + + return $this; + } + + /** + * @param array $elements + * @param bool $extended + * @return void + */ + protected function filterElements(array &$elements, bool $extended = false): void + { + // Markdown storage conversion to page structure. + if (array_key_exists('content', $elements)) { + $elements['markdown'] = $elements['content']; + unset($elements['content']); + } + + if (!$extended) { + $folder = !empty($elements['folder']) ? trim($elements['folder']) : ''; + + if ($folder) { + $order = !empty($elements['order']) ? (int)$elements['order'] : null; + // TODO: broken + $elements['storage_key'] = $order ? sprintf('%02d.%s', $order, $folder) : $folder; + } + } + + parent::filterElements($elements); + } + + /** + * @param string $field + * @return int|null + */ + protected function getFieldTimestamp(string $field): ?int + { + $date = $this->getFieldDateTime($field); + + return $date ? $date->getTimestamp() : null; + } + + /** + * @param string $field + * @return DateTime|null + */ + protected function getFieldDateTime(string $field): ?DateTime + { + try { + $value = $this->getProperty($field); + if (is_numeric($value)) { + $value = '@' . $value; + } + $date = $value ? new DateTime($value) : null; + } catch (Exception $e) { + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + $debugger->addException($e); + + $date = null; + } + + return $date; + } + + /** + * @return UserCollectionInterface|null + * @internal + */ + protected function loadAccounts() + { + return Grav::instance()['accounts'] ?? null; + } +} diff --git a/system/src/Grav/Framework/Flex/Pages/Traits/PageAuthorsTrait.php b/system/src/Grav/Framework/Flex/Pages/Traits/PageAuthorsTrait.php new file mode 100644 index 0000000..ba02176 --- /dev/null +++ b/system/src/Grav/Framework/Flex/Pages/Traits/PageAuthorsTrait.php @@ -0,0 +1,249 @@ + */ + private $_authors; + /** @var array|null */ + private $_permissionsCache; + + /** + * Returns true if object has the named author. + * + * @param string $username + * @return bool + */ + public function hasAuthor(string $username): bool + { + $authors = (array)$this->getNestedProperty('header.permissions.authors'); + if (empty($authors)) { + return false; + } + + foreach ($authors as $author) { + if ($username === $author) { + return true; + } + } + + return false; + } + + /** + * Get list of all author objects. + * + * @return array + */ + public function getAuthors(): array + { + if (null === $this->_authors) { + $this->_authors = $this->loadAuthors($this->getNestedProperty('header.permissions.authors', [])); + } + + return $this->_authors; + } + + /** + * @param bool $inherit + * @return array + */ + public function getPermissions(bool $inherit = false) + { + if (null === $this->_permissionsCache) { + $permissions = []; + if ($inherit && $this->getNestedProperty('header.permissions.inherit', true)) { + $parent = $this->parent(); + if ($parent && method_exists($parent, 'getPermissions')) { + $permissions = $parent->getPermissions($inherit); + } + } + + $this->_permissionsCache = $this->loadPermissions($permissions); + } + + return $this->_permissionsCache; + } + + /** + * @param iterable $authors + * @return array + */ + protected function loadAuthors(iterable $authors): array + { + $accounts = $this->loadAccounts(); + if (null === $accounts || empty($authors)) { + return []; + } + + $list = []; + foreach ($authors as $username) { + if (!is_string($username)) { + throw new InvalidArgumentException('Iterable should return username (string).', 500); + } + $list[] = $accounts->load($username); + } + + return $list; + } + + /** + * @param string $action + * @param string|null $scope + * @param UserInterface|null $user + * @param bool $isAuthor + * @return bool|null + */ + public function isParentAuthorized(string $action, string $scope = null, UserInterface $user = null, bool $isAuthor = false): ?bool + { + $scope = $scope ?? $this->getAuthorizeScope(); + + $isMe = null === $user; + if ($isMe) { + $user = $this->getActiveUser(); + } + + if (null === $user) { + return false; + } + + return $this->isAuthorizedByGroup($user, $action, $scope, $isMe, $isAuthor); + } + + /** + * @param UserInterface $user + * @param string $action + * @param string $scope + * @param bool $isMe + * @return bool|null + */ + protected function isAuthorizedOverride(UserInterface $user, string $action, string $scope, bool $isMe): ?bool + { + if ($action === 'delete' && $this->root()) { + // Do not allow deleting root. + return false; + } + + $isAuthor = !$isMe || $user->authorized ? $this->hasAuthor($user->username) : false; + + return $this->isAuthorizedByGroup($user, $action, $scope, $isMe, $isAuthor) ?? parent::isAuthorizedOverride($user, $action, $scope, $isMe); + } + + /** + * Group authorization works as follows: + * + * 1. if any of the groups deny access, return false + * 2. else if any of the groups allow access, return true + * 3. else return null + * + * @param UserInterface $user + * @param string $action + * @param string $scope + * @param bool $isMe + * @param bool $isAuthor + * @return bool|null + */ + protected function isAuthorizedByGroup(UserInterface $user, string $action, string $scope, bool $isMe, bool $isAuthor): ?bool + { + $authorized = null; + + // In admin we want to check against group permissions. + $pageGroups = $this->getPermissions(); + $userGroups = (array)$user->groups; + + /** @var Access $access */ + foreach ($pageGroups as $group => $access) { + if ($group === 'defaults') { + // Special defaults permissions group does not apply to guest. + if ($isMe && !$user->authorized) { + continue; + } + } elseif ($group === 'authors') { + if (!$isAuthor) { + continue; + } + } elseif (!in_array($group, $userGroups, true)) { + continue; + } + + $auth = $access->authorize($action); + if (is_bool($auth)) { + if ($auth === false) { + return false; + } + + $authorized = true; + } + } + + if (null === $authorized && $this->getNestedProperty('header.permissions.inherit', true)) { + // Authorize against parent page. + $parent = $this->parent(); + if ($parent && method_exists($parent, 'isParentAuthorized')) { + $authorized = $parent->isParentAuthorized($action, $scope, !$isMe ? $user : null, $isAuthor); + } + } + + return $authorized; + } + + /** + * @param array $parent + * @return array + */ + protected function loadPermissions(array $parent = []): array + { + static $rules = [ + 'c' => 'create', + 'r' => 'read', + 'u' => 'update', + 'd' => 'delete', + 'p' => 'publish', + 'l' => 'list' + ]; + + $permissions = $this->getNestedProperty('header.permissions.groups'); + $name = $this->root() ? '' : '/' . $this->getKey(); + + $list = []; + if (is_array($permissions)) { + foreach ($permissions as $group => $access) { + $list[$group] = new Access($access, $rules, $name); + } + } + foreach ($parent as $group => $access) { + if (isset($list[$group])) { + $object = $list[$group]; + } else { + $object = new Access([], $rules, $name); + $list[$group] = $object; + } + + $object->inherit($access); + } + + return $list; + } +} diff --git a/system/src/Grav/Framework/Flex/Pages/Traits/PageContentTrait.php b/system/src/Grav/Framework/Flex/Pages/Traits/PageContentTrait.php new file mode 100644 index 0000000..2eb3789 --- /dev/null +++ b/system/src/Grav/Framework/Flex/Pages/Traits/PageContentTrait.php @@ -0,0 +1,842 @@ + 'slug', + 'routes' => false, + 'title' => 'title', + 'language' => 'language', + 'template' => 'template', + 'menu' => 'menu', + 'routable' => 'routable', + 'visible' => 'visible', + 'redirect' => 'redirect', + 'external_url' => false, + 'order_dir' => 'orderDir', + 'order_by' => 'orderBy', + 'order_manual' => 'orderManual', + 'dateformat' => 'dateformat', + 'date' => 'date', + 'markdown_extra' => false, + 'taxonomy' => 'taxonomy', + 'max_count' => 'maxCount', + 'process' => 'process', + 'published' => 'published', + 'publish_date' => 'publishDate', + 'unpublish_date' => 'unpublishDate', + 'expires' => 'expires', + 'cache_control' => 'cacheControl', + 'etag' => 'eTag', + 'last_modified' => 'lastModified', + 'ssl' => 'ssl', + 'template_format' => 'templateFormat', + 'debugger' => false, + ]; + + /** @var array */ + protected static $calculatedProperties = [ + 'name' => 'name', + 'parent' => 'parent', + 'parent_key' => 'parentStorageKey', + 'folder' => 'folder', + 'order' => 'order', + 'template' => 'template', + ]; + + /** @var object|null */ + protected $header; + + /** @var string|null */ + protected $_summary; + + /** @var string|null */ + protected $_content; + + /** + * Method to normalize the route. + * + * @param string $route + * @return string + * @internal + */ + public static function normalizeRoute($route): string + { + $case_insensitive = Grav::instance()['config']->get('system.force_lowercase_urls'); + + return $case_insensitive ? mb_strtolower($route) : $route; + } + + /** + * @inheritdoc + * @return Header + */ + public function header($var = null) + { + if (null !== $var) { + $this->setProperty('header', $var); + } + + return $this->getProperty('header'); + } + + /** + * @inheritdoc + */ + public function summary($size = null, $textOnly = false): string + { + return $this->processSummary($size, $textOnly); + } + + /** + * @inheritdoc + */ + public function setSummary($summary): void + { + $this->_summary = $summary; + } + + /** + * @inheritdoc + * @throws Exception + */ + public function content($var = null): string + { + if (null !== $var) { + $this->_content = $var; + } + + return $this->_content ?? $this->processContent($this->getRawContent()); + } + + /** + * @inheritdoc + */ + public function getRawContent(): string + { + return $this->_content ?? $this->getArrayProperty('markdown') ?? ''; + } + + /** + * @inheritdoc + */ + public function setRawContent($content): void + { + $this->_content = $content ?? ''; + } + + /** + * @inheritdoc + */ + public function rawMarkdown($var = null): string + { + if ($var !== null) { + $this->setProperty('markdown', $var); + } + + return $this->getProperty('markdown') ?? ''; + } + + /** + * @inheritdoc + * + * Implement by calling: + * + * $test = new \stdClass(); + * $value = $this->pageContentValue($name, $test); + * if ($value !== $test) { + * return $value; + * } + * return parent::value($name, $default); + */ + abstract public function value($name, $default = null, $separator = null); + + /** + * @inheritdoc + */ + public function media($var = null): Media + { + if ($var instanceof Media) { + $this->setProperty('media', $var); + } + + return $this->getProperty('media'); + } + + /** + * @inheritdoc + */ + public function title($var = null): string + { + return $this->loadHeaderProperty( + 'title', + $var, + function ($value) { + return trim($value ?? ($this->root() ? '' : ucfirst($this->slug()))); + } + ); + } + + /** + * @inheritdoc + */ + public function menu($var = null): string + { + return $this->loadHeaderProperty( + 'menu', + $var, + function ($value) { + return trim($value ?: $this->title()); + } + ); + } + + /** + * @inheritdoc + */ + public function visible($var = null): bool + { + $value = $this->loadHeaderProperty( + 'visible', + $var, + function ($value) { + return ($value ?? $this->order() !== false) && !$this->isModule(); + } + ); + + return $value && $this->published(); + } + + /** + * @inheritdoc + */ + public function published($var = null): bool + { + return $this->loadHeaderProperty( + 'published', + $var, + static function ($value) { + return (bool)($value ?? true); + } + ); + } + + /** + * @inheritdoc + */ + public function publishDate($var = null): ?int + { + return $this->loadHeaderProperty( + 'publish_date', + $var, + function ($value) { + return $value ? Utils::date2timestamp($value, $this->getProperty('dateformat')) : null; + } + ); + } + + /** + * @inheritdoc + */ + public function unpublishDate($var = null): ?int + { + return $this->loadHeaderProperty( + 'unpublish_date', + $var, + function ($value) { + return $value ? Utils::date2timestamp($value, $this->getProperty('dateformat')) : null; + } + ); + } + + /** + * @inheritdoc + */ + public function process($var = null): array + { + return $this->loadHeaderProperty( + 'process', + $var, + function ($value) { + $value = array_replace(Grav::instance()['config']->get('system.pages.process', []), is_array($value) ? $value : []); + foreach ($value as $process => $status) { + $value[$process] = (bool)$status; + } + + return $value; + } + ); + } + + /** + * @inheritdoc + */ + public function slug($var = null) + { + return $this->loadHeaderProperty( + 'slug', + $var, + function ($value) { + if (is_string($value)) { + return $value; + } + + $folder = $this->folder(); + if (null === $folder) { + return null; + } + + $folder = preg_replace(static::PAGE_ORDER_PREFIX_REGEX, '', $folder); + if (null === $folder) { + return null; + } + + return static::normalizeRoute($folder); + } + ); + } + + /** + * @inheritdoc + */ + public function order($var = null) + { + $property = $this->loadProperty( + 'order', + $var, + function ($value) { + if (null === $value) { + $folder = $this->folder(); + if (null !== $folder) { + preg_match(static::PAGE_ORDER_REGEX, $folder, $order); + } + + $value = $order[1] ?? false; + } + + if ($value === '') { + $value = false; + } + if ($value !== false) { + $value = (int)$value; + } + + return $value; + } + ); + + return $property !== false ? sprintf('%02d.', $property) : false; + } + + /** + * @inheritdoc + */ + public function id($var = null): string + { + $property = 'id'; + $value = null === $var ? $this->getProperty($property) : null; + if (null === $value) { + $value = $this->language() . ($var ?? ($this->modified() . md5('flex-' . $this->getFlexType() . '-' . $this->getKey()))); + + $this->setProperty($property, $value); + if ($this->doHasProperty($property)) { + $value = $this->getProperty($property); + } + } + + return $value; + } + + /** + * @inheritdoc + */ + public function modified($var = null): int + { + $property = 'modified'; + $value = null === $var ? $this->getProperty($property) : null; + if (null === $value) { + $value = (int)($var ?: $this->getTimestamp()); + + $this->setProperty($property, $value); + if ($this->doHasProperty($property)) { + $value = $this->getProperty($property); + } + } + + return $value; + } + + /** + * @inheritdoc + */ + public function lastModified($var = null): bool + { + return $this->loadHeaderProperty( + 'last_modified', + $var, + static function ($value) { + return (bool)($value ?? Grav::instance()['config']->get('system.pages.last_modified')); + } + ); + } + + /** + * @inheritdoc + */ + public function date($var = null): int + { + return $this->loadHeaderProperty( + 'date', + $var, + function ($value) { + $value = $value ? Utils::date2timestamp($value, $this->getProperty('dateformat')) : false; + + return $value ?: $this->modified(); + } + ); + } + + /** + * @inheritdoc + */ + public function dateformat($var = null): ?string + { + return $this->loadHeaderProperty( + 'dateformat', + $var, + static function ($value) { + return $value; + } + ); + } + + /** + * @inheritdoc + */ + public function taxonomy($var = null): array + { + return $this->loadHeaderProperty( + 'taxonomy', + $var, + static function ($value) { + if (is_array($value)) { + // make sure first level are arrays + array_walk($value, static function (&$val) { + $val = (array) $val; + }); + // make sure all values are strings + array_walk_recursive($value, static function (&$val) { + $val = (string) $val; + }); + } + + return $value ?? []; + } + ); + } + + /** + * @inheritdoc + */ + public function shouldProcess($process): bool + { + $test = $this->process(); + + return !empty($test[$process]); + } + + /** + * @inheritdoc + */ + public function isPage(): bool + { + return !in_array($this->template(), ['', 'folder'], true); + } + + /** + * @inheritdoc + */ + public function isDir(): bool + { + return !$this->isPage(); + } + + /** + * @return bool + */ + public function isModule(): bool + { + return $this->modularTwig(); + } + + /** + * @param Header|stdClass|array|null $value + * @return Header + */ + protected function offsetLoad_header($value) + { + if ($value instanceof Header) { + return $value; + } + + if (null === $value) { + $value = []; + } elseif ($value instanceof stdClass) { + $value = (array)$value; + } + + return new Header($value); + } + + /** + * @param Header|stdClass|array|null $value + * @return Header + */ + protected function offsetPrepare_header($value) + { + return $this->offsetLoad_header($value); + } + + /** + * @param Header|null $value + * @return array + */ + protected function offsetSerialize_header(?Header $value) + { + return $value ? $value->toArray() : []; + } + + /** + * @param string $name + * @param mixed|null $default + * @return mixed + */ + protected function pageContentValue($name, $default = null) + { + switch ($name) { + case 'frontmatter': + $frontmatter = $this->getArrayProperty('frontmatter'); + if ($frontmatter === null) { + $header = $this->prepareStorage()['header'] ?? null; + if ($header) { + $formatter = new YamlFormatter(); + $frontmatter = $formatter->encode($header); + } else { + $frontmatter = ''; + } + } + return $frontmatter; + case 'content': + return $this->getProperty('markdown'); + case 'order': + return (string)$this->order(); + case 'menu': + return $this->menu(); + case 'ordering': + return $this->order() !== false ? '1' : '0'; + case 'folder': + $folder = $this->folder(); + + return null !== $folder ? preg_replace(static::PAGE_ORDER_PREFIX_REGEX, '', $folder) : ''; + case 'slug': + return $this->slug(); + case 'published': + return $this->published(); + case 'visible': + return $this->visible(); + case 'media': + return $this->media()->all(); + case 'media.file': + return $this->media()->files(); + case 'media.video': + return $this->media()->videos(); + case 'media.image': + return $this->media()->images(); + case 'media.audio': + return $this->media()->audios(); + } + + return $default; + } + + /** + * @param int|null $size + * @param bool $textOnly + * @return string + */ + protected function processSummary($size = null, $textOnly = false): string + { + $config = (array)Grav::instance()['config']->get('site.summary'); + $config_page = (array)$this->getNestedProperty('header.summary'); + if ($config_page) { + $config = array_merge($config, $config_page); + } + + // Summary is not enabled, return the whole content. + if (empty($config['enabled'])) { + return $this->content(); + } + + $content = $this->_summary ?? $this->content(); + if ($textOnly) { + $content = strip_tags($content); + } + $content_size = mb_strwidth($content, 'utf-8'); + $summary_size = $this->_summary !== null ? $content_size : $this->getProperty('summary_size'); + + // Return calculated summary based on summary divider's position. + $format = $config['format'] ?? ''; + + // Return entire page content on wrong/unknown format. + if ($format !== 'long' && $format !== 'short') { + return $content; + } + + if ($format === 'short' && null !== $summary_size) { + // Slice the string on breakpoint. + if ($content_size > $summary_size) { + return mb_substr($content, 0, $summary_size); + } + + return $content; + } + + // If needed, get summary size from the config. + $size = $size ?? $config['size'] ?? null; + + // Return calculated summary based on defaults. + $size = is_numeric($size) ? (int)$size : -1; + if ($size < 0) { + $size = 300; + } + + // If the size is zero or smaller than the summary limit, return the entire page content. + if ($size === 0 || $content_size <= $size) { + return $content; + } + + // Only return string but not html, wrap whatever html tag you want when using. + if ($textOnly) { + return mb_strimwidth($content, 0, $size, '...', 'UTF-8'); + } + + $summary = Utils::truncateHTML($content, $size); + + return html_entity_decode($summary, ENT_COMPAT | ENT_HTML5, 'UTF-8'); + } + + /** + * Gets and Sets the content based on content portion of the .md file + * + * @param string $content + * @return string + * @throws Exception + */ + protected function processContent($content): string + { + $content = is_string($content) ? $content : ''; + $grav = Grav::instance(); + + /** @var Config $config */ + $config = $grav['config']; + + $process_markdown = $this->shouldProcess('markdown'); + $process_twig = $this->shouldProcess('twig') || $this->isModule(); + $cache_enable = $this->getNestedProperty('header.cache_enable') ?? $config->get('system.cache.enabled', true); + + $twig_first = $this->getNestedProperty('header.twig_first') ?? $config->get('system.pages.twig_first', false); + $never_cache_twig = $this->getNestedProperty('header.never_cache_twig') ?? $config->get('system.pages.never_cache_twig', false); + + if ($cache_enable) { + $cache = $this->getCache('render'); + $key = md5($this->getCacheKey() . '-content'); + $cached = $cache->get($key); + if ($cached && $cached['checksum'] === $this->getCacheChecksum()) { + $this->_content = $cached['content'] ?? ''; + $this->_content_meta = $cached['content_meta'] ?? null; + + if ($process_twig && $never_cache_twig) { + $this->_content = $this->processTwig($this->_content); + } + } + } + + if (null === $this->_content) { + $markdown_options = []; + if ($process_markdown) { + // Build markdown options. + $markdown_options = (array)$config->get('system.pages.markdown'); + $markdown_page_options = (array)$this->getNestedProperty('header.markdown'); + if ($markdown_page_options) { + $markdown_options = array_merge($markdown_options, $markdown_page_options); + } + + // pages.markdown_extra is deprecated, but still check it... + if (!isset($markdown_options['extra'])) { + $extra = $this->getNestedProperty('header.markdown_extra') ?? $config->get('system.pages.markdown_extra'); + if (null !== $extra) { + user_error('Configuration option \'system.pages.markdown_extra\' is deprecated since Grav 1.5, use \'system.pages.markdown.extra\' instead', E_USER_DEPRECATED); + + $markdown_options['extra'] = $extra; + } + } + } + $options = [ + 'markdown' => $markdown_options, + 'images' => $config->get('system.images', []) + ]; + + $this->_content = $content; + $grav->fireEvent('onPageContentRaw', new Event(['page' => $this])); + + if ($twig_first && !$never_cache_twig) { + if ($process_twig) { + $this->_content = $this->processTwig($this->_content); + } + + if ($process_markdown) { + $this->_content = $this->processMarkdown($this->_content, $options); + } + + // Content Processed but not cached yet + $grav->fireEvent('onPageContentProcessed', new Event(['page' => $this])); + } else { + if ($process_markdown) { + $options['keep_twig'] = $process_twig; + $this->_content = $this->processMarkdown($this->_content, $options); + } + + // Content Processed but not cached yet + $grav->fireEvent('onPageContentProcessed', new Event(['page' => $this])); + + if ($cache_enable && $never_cache_twig) { + $this->cachePageContent(); + } + + if ($process_twig) { + \assert(is_string($this->_content)); + $this->_content = $this->processTwig($this->_content); + } + } + + if ($cache_enable && !$never_cache_twig) { + $this->cachePageContent(); + } + } + + \assert(is_string($this->_content)); + + // Handle summary divider + $delimiter = $config->get('site.summary.delimiter', '==='); + $divider_pos = mb_strpos($this->_content, "

    {$delimiter}

    "); + if ($divider_pos !== false) { + $this->setProperty('summary_size', $divider_pos); + $this->_content = str_replace("

    {$delimiter}

    ", '', $this->_content); + } + + // Fire event when Page::content() is called + $grav->fireEvent('onPageContent', new Event(['page' => $this])); + + return $this->_content; + } + + /** + * Process the Twig page content. + * + * @param string $content + * @return string + */ + protected function processTwig($content): string + { + /** @var Twig $twig */ + $twig = Grav::instance()['twig']; + + /** @var PageInterface $this */ + return $twig->processPage($this, $content); + } + + /** + * Process the Markdown content. + * + * Uses Parsedown or Parsedown Extra depending on configuration. + * + * @param string $content + * @param array $options + * @return string + * @throws Exception + */ + protected function processMarkdown($content, array $options = []): string + { + /** @var PageInterface $self */ + $self = $this; + + $excerpts = new Excerpts($self, $options); + + // Initialize the preferred variant of markdown parser. + if (isset($options['extra'])) { + $parsedown = new ParsedownExtra($excerpts); + } else { + $parsedown = new Parsedown($excerpts); + } + + $keepTwig = (bool)($options['keep_twig'] ?? false); + if ($keepTwig) { + $token = [ + '/' . Utils::generateRandomString(3), + Utils::generateRandomString(3) . '/' + ]; + // Base64 encode any twig. + $content = preg_replace_callback( + ['/({#.*?#})/mu', '/({{.*?}})/mu', '/({%.*?%})/mu'], + static function ($matches) use ($token) { return $token[0] . base64_encode($matches[1]) . $token[1]; }, + $content + ); + } + + $content = $parsedown->text($content); + + if ($keepTwig) { + // Base64 decode the encoded twig. + $content = preg_replace_callback( + ['`' . $token[0] . '([A-Za-z0-9+/]+={0,2})' . $token[1] . '`mu'], + static function ($matches) { return base64_decode($matches[1]); }, + $content + ); + } + + return $content; + } + + abstract protected function loadHeaderProperty(string $property, $var, callable $filter); +} diff --git a/system/src/Grav/Framework/Flex/Pages/Traits/PageLegacyTrait.php b/system/src/Grav/Framework/Flex/Pages/Traits/PageLegacyTrait.php new file mode 100644 index 0000000..f989a2e --- /dev/null +++ b/system/src/Grav/Framework/Flex/Pages/Traits/PageLegacyTrait.php @@ -0,0 +1,1124 @@ +getFlexDirectory()->getStorage(); + if (method_exists($storage, 'readRaw')) { + return $storage->readRaw($this->getStorageKey()); + } + + $array = $this->prepareStorage(); + $formatter = new MarkdownFormatter(); + + return $formatter->encode($array); + } + + /** + * Gets and Sets the page frontmatter + * + * @param string|null $var + * @return string + */ + public function frontmatter($var = null): string + { + if (null !== $var) { + $formatter = new YamlFormatter(); + $this->setProperty('frontmatter', $var); + $this->setProperty('header', $formatter->decode($var)); + + return $var; + } + + $storage = $this->getFlexDirectory()->getStorage(); + if (method_exists($storage, 'readFrontmatter')) { + return $storage->readFrontmatter($this->getStorageKey()); + } + + $array = $this->prepareStorage(); + $formatter = new YamlFormatter(); + + return $formatter->encode($array['header'] ?? []); + } + + /** + * Modify a header value directly + * + * @param string $key + * @param string|array $value + * @return void + */ + public function modifyHeader($key, $value): void + { + $this->setNestedProperty("header.{$key}", $value); + } + + /** + * @return int + */ + public function httpResponseCode(): int + { + $code = (int)$this->getNestedProperty('header.http_response_code'); + + return $code ?: 200; + } + + /** + * @return array + */ + public function httpHeaders(): array + { + $headers = []; + + $format = $this->templateFormat(); + $cache_control = $this->cacheControl(); + $expires = $this->expires(); + + // Set Content-Type header. + $headers['Content-Type'] = Utils::getMimeByExtension($format, 'text/html'); + + // Calculate Expires Headers if set to > 0. + if ($expires > 0) { + $expires_date = gmdate('D, d M Y H:i:s', time() + $expires) . ' GMT'; + if (!$cache_control) { + $headers['Cache-Control'] = 'max-age=' . $expires; + } + $headers['Expires'] = $expires_date; + } + + // Set Cache-Control header. + if ($cache_control) { + $headers['Cache-Control'] = strtolower($cache_control); + } + + // Set Last-Modified header. + if ($this->lastModified()) { + $last_modified_date = gmdate('D, d M Y H:i:s', $this->modified()) . ' GMT'; + $headers['Last-Modified'] = $last_modified_date; + } + + // Calculate ETag based on the serialized page and modified time. + if ($this->eTag()) { + $headers['ETag'] = '1'; + } + + // Set Vary: Accept-Encoding header. + $grav = Grav::instance(); + if ($grav['config']->get('system.pages.vary_accept_encoding', false)) { + $headers['Vary'] = 'Accept-Encoding'; + } + + return $headers; + } + + /** + * Get the contentMeta array and initialize content first if it's not already + * + * @return array + */ + public function contentMeta(): array + { + // Content meta is generated during the content is being rendered, so make sure we have done it. + $this->content(); + + return $this->_content_meta ?? []; + } + + /** + * Add an entry to the page's contentMeta array + * + * @param string $name + * @param string $value + * @return void + */ + public function addContentMeta($name, $value): void + { + $this->_content_meta[$name] = $value; + } + + /** + * Return the whole contentMeta array as it currently stands + * + * @param string|null $name + * @return string|array|null + */ + public function getContentMeta($name = null) + { + if ($name) { + return $this->_content_meta[$name] ?? null; + } + + return $this->_content_meta ?? []; + } + + /** + * Sets the whole content meta array in one shot + * + * @param array $content_meta + * @return array + */ + public function setContentMeta($content_meta): array + { + return $this->_content_meta = $content_meta; + } + + /** + * Fires the onPageContentProcessed event, and caches the page content using a unique ID for the page + */ + public function cachePageContent(): void + { + $value = [ + 'checksum' => $this->getCacheChecksum(), + 'content' => $this->_content, + 'content_meta' => $this->_content_meta + ]; + + $cache = $this->getCache('render'); + $key = md5($this->getCacheKey() . '-content'); + + $cache->set($key, $value); + } + + /** + * Get file object to the page. + * + * @return MarkdownFile|null + */ + public function file(): ?MarkdownFile + { + // TODO: + throw new RuntimeException(__METHOD__ . '(): Not Implemented'); + } + + /** + * 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 PageInterface $parent New parent page. + * @return $this + */ + public function move(PageInterface $parent) + { + if ($this->route() === $parent->route()) { + throw new RuntimeException('Failed: Cannot set page parent to self'); + } + $rawRoute = $this->rawRoute(); + if ($rawRoute && Utils::startsWith($parent->rawRoute(), $rawRoute)) { + throw new RuntimeException('Failed: Cannot set page parent to a child of current page'); + } + + $this->storeOriginal(); + + // TODO: + throw new RuntimeException(__METHOD__ . '(): Not Implemented'); + } + + /** + * 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 PageInterface|null $parent New parent page. + * @return $this + */ + public function copy(PageInterface $parent = null) + { + $this->storeOriginal(); + + $filesystem = Filesystem::getInstance(false); + + $parentStorageKey = ltrim($filesystem->dirname("/{$this->getMasterKey()}"), '/'); + + /** @var FlexPageIndex> $index */ + $index = $this->getFlexDirectory()->getIndex(); + + if ($parent) { + if ($parent instanceof FlexPageObject) { + $k = $parent->getMasterKey(); + if ($k !== $parentStorageKey) { + $parentStorageKey = $k; + } + } else { + throw new RuntimeException('Cannot copy page, parent is of unknown type'); + } + } else { + $parent = $parentStorageKey + ? $this->getFlexDirectory()->getObject($parentStorageKey, 'storage_key') + : (method_exists($index, 'getRoot') ? $index->getRoot() : null); + } + + // Find non-existing key. + $parentKey = $parent ? $parent->getKey() : ''; + if ($this instanceof FlexPageObject) { + $key = trim($parentKey . '/' . $this->folder(), '/'); + $key = preg_replace(static::PAGE_ORDER_PREFIX_REGEX, '', $key); + \assert(is_string($key)); + } else { + $key = trim($parentKey . '/' . Utils::basename($this->getKey()), '/'); + } + + if ($index->containsKey($key)) { + $key = preg_replace('/\d+$/', '', $key); + $i = 1; + do { + $i++; + $test = "{$key}{$i}"; + } while ($index->containsKey($test)); + $key = $test; + } + $folder = Utils::basename($key); + + // Get the folder name. + $order = $this->getProperty('order'); + if ($order) { + $order++; + } + + $parts = []; + if ($parentStorageKey !== '') { + $parts[] = $parentStorageKey; + } + $parts[] = $order ? sprintf('%02d.%s', $order, $folder) : $folder; + + // Finally update the object. + $this->setKey($key); + $this->setStorageKey(implode('/', $parts)); + + $this->markAsCopy(); + + return $this; + } + + /** + * Get the blueprint name for this page. Use the blueprint form field if set + * + * @return string + */ + public function blueprintName(): string + { + if (!isset($_POST['blueprint'])) { + return $this->template(); + } + + $post_value = $_POST['blueprint']; + $sanitized_value = htmlspecialchars(strip_tags($post_value), ENT_QUOTES, 'UTF-8'); + + return $sanitized_value ?: $this->template(); + } + + /** + * Validate page header. + * + * @return void + * @throws Exception + */ + public function validate(): void + { + $blueprint = $this->getBlueprint(); + $blueprint->validate($this->toArray()); + } + + /** + * Filter page header from illegal contents. + * + * @return void + */ + public function filter(): void + { + $blueprints = $this->getBlueprint(); + $values = $blueprints->filter($this->toArray()); + if ($values && isset($values['header'])) { + $this->header($values['header']); + } + } + + /** + * Get unknown header variables. + * + * @return array + */ + public function extra(): array + { + $data = $this->prepareStorage(); + + return $this->getBlueprint()->extra((array)($data['header'] ?? []), 'header.'); + } + + /** + * Convert page to an array. + * + * @return array + */ + public function toArray(): array + { + return [ + 'header' => (array)$this->header(), + 'content' => (string)$this->getFormValue('content') + ]; + } + + /** + * Convert page to YAML encoded string. + * + * @return string + */ + public function toYaml(): string + { + return Yaml::dump($this->toArray(), 20); + } + + /** + * Convert page to JSON encoded string. + * + * @return string + */ + public function toJson(): string + { + $json = json_encode($this->toArray()); + if (!is_string($json)) { + throw new RuntimeException('Internal error'); + } + + return $json; + } + + /** + * Gets and sets the name field. If no name field is set, it will return 'default.md'. + * + * @param string|null $var The name of this page. + * @return string The name of this page. + */ + public function name($var = null): string + { + return $this->loadProperty( + 'name', + $var, + function ($value) { + $value = $value ?? $this->getMetaData()['template'] ?? 'default'; + if (!preg_match('/\.md$/', $value)) { + $language = $this->language(); + if ($language) { + // TODO: better language support + $value .= ".{$language}"; + } + $value .= '.md'; + } + $value = preg_replace('|^modular/|', '', $value); + + $this->unsetProperty('template'); + + return $value; + } + ); + } + + /** + * Returns child page type. + * + * @return string + */ + public function childType(): string + { + return (string)$this->getNestedProperty('header.child_type'); + } + + /** + * 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|null $var the template name + * @return string the template name + */ + public function template($var = null): string + { + return $this->loadHeaderProperty( + 'template', + $var, + function ($value) { + return trim($value ?? (($this->isModule() ? 'modular/' : '') . str_replace($this->extension(), '', $this->name()))); + } + ); + } + + /** + * Allows a page to override the output render format, usually the extension provided in the URL. + * (e.g. `html`, `json`, `xml`, etc). + * + * @param string|null $var + * @return string + */ + public function templateFormat($var = null): string + { + return $this->loadHeaderProperty( + 'template_format', + $var, + function ($value) { + return ltrim($value ?? $this->getNestedProperty('header.append_url_extension') ?: Utils::getPageFormat(), '.'); + } + ); + } + + /** + * Gets and sets the extension field. + * + * @param string|null $var + * @return string + */ + public function extension($var = null): string + { + if (null !== $var) { + $this->setProperty('format', $var); + } + + $language = $this->language(); + if ($language) { + $language = '.' . $language; + } + $format = '.' . ($this->getProperty('format') ?? Utils::pathinfo($this->name(), PATHINFO_EXTENSION)); + + return $language . $format; + } + + /** + * Gets and sets the expires field. If not set will return the default + * + * @param int|null $var The new expires value. + * @return int The expires value + */ + public function expires($var = null): int + { + return $this->loadHeaderProperty( + 'expires', + $var, + static function ($value) { + return (int)($value ?? Grav::instance()['config']->get('system.pages.expires')); + } + ); + } + + /** + * Gets and sets the cache-control property. If not set it will return the default value (null) + * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control for more details on valid options + * + * @param string|null $var + * @return string|null + */ + public function cacheControl($var = null): ?string + { + return $this->loadHeaderProperty( + 'cache_control', + $var, + static function ($value) { + return ((string)($value ?? Grav::instance()['config']->get('system.pages.cache_control'))) ?: null; + } + ); + } + + /** + * @param bool|null $var + * @return bool|null + */ + public function ssl($var = null): ?bool + { + return $this->loadHeaderProperty( + 'ssl', + $var, + static function ($value) { + return $value ? (bool)$value : null; + } + ); + } + + /** + * Returns the state of the debugger override setting for this page + * + * @return bool + */ + public function debugger(): bool + { + return (bool)$this->getNestedProperty('header.debugger', true); + } + + /** + * Function to merge page metadata tags and build an array of Metadata objects + * that can then be rendered in the page. + * + * @param array|null $var an Array of metadata values to set + * @return array an Array of metadata values for the page + */ + public function metadata($var = null): array + { + if ($var !== null) { + $this->_metadata = (array)$var; + } + + // if not metadata yet, process it. + if (null === $this->_metadata) { + $this->_metadata = []; + + $config = Grav::instance()['config']; + + // Set the Generator tag + $defaultMetadata = ['generator' => 'GravCMS']; + $siteMetadata = $config->get('site.metadata', []); + $headerMetadata = $this->getNestedProperty('header.metadata', []); + + // Get initial metadata for the page + $metadata = array_merge($defaultMetadata, $siteMetadata, $headerMetadata); + + $header_tag_http_equivs = ['content-type', 'default-style', 'refresh', 'x-ua-compatible', 'content-security-policy']; + $escape = !$config->get('system.strict_mode.twig_compat', false) || $config->get('system.twig.autoescape', true); + + // Build an array of meta objects.. + foreach ($metadata as $key => $value) { + // Lowercase the key + $key = strtolower($key); + + // If this is a property type metadata: "og", "twitter", "facebook" etc + // Backward compatibility for nested arrays in metas + if (is_array($value)) { + foreach ($value as $property => $prop_value) { + $prop_key = $key . ':' . $property; + $this->_metadata[$prop_key] = [ + 'name' => $prop_key, + 'property' => $prop_key, + 'content' => $escape ? htmlspecialchars($prop_value, ENT_QUOTES | ENT_HTML5, 'UTF-8') : $prop_value + ]; + } + } elseif ($value) { + // If it this is a standard meta data type + if (in_array($key, $header_tag_http_equivs, true)) { + $this->_metadata[$key] = [ + 'http_equiv' => $key, + 'content' => $escape ? htmlspecialchars($value, ENT_COMPAT, 'UTF-8') : $value + ]; + } elseif ($key === 'charset') { + $this->_metadata[$key] = ['charset' => $escape ? htmlspecialchars($value, ENT_QUOTES | ENT_HTML5, 'UTF-8') : $value]; + } else { + // if it's a social metadata with separator, render as property + $separator = strpos($key, ':'); + $hasSeparator = $separator && $separator < strlen($key) - 1; + $entry = [ + 'content' => $escape ? htmlspecialchars($value, ENT_QUOTES | ENT_HTML5, 'UTF-8') : $value + ]; + + if ($hasSeparator && !Utils::startsWith($key, 'twitter')) { + $entry['property'] = $key; + } else { + $entry['name'] = $key; + } + + $this->_metadata[$key] = $entry; + } + } + } + } + + return $this->_metadata; + } + + /** + * Reset the metadata and pull from header again + */ + public function resetMetadata(): void + { + $this->_metadata = null; + } + + /** + * Gets and sets the option to show the etag header for the page. + * + * @param bool|null $var show etag header + * @return bool show etag header + */ + public function eTag($var = null): bool + { + return $this->loadHeaderProperty( + 'etag', + $var, + static function ($value) { + return (bool)($value ?? Grav::instance()['config']->get('system.pages.etag')); + } + ); + } + + /** + * Gets and sets the path to the .md file for this Page object. + * + * @param string|null $var the file path + * @return string|null the file path + */ + public function filePath($var = null): ?string + { + if (null !== $var) { + // TODO: + throw new RuntimeException(__METHOD__ . '(string): Not Implemented'); + } + + $folder = $this->getStorageFolder(); + if (!$folder) { + return null; + } + + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + $folder = $locator->isStream($folder) ? $locator->getResource($folder) : GRAV_ROOT . "/{$folder}"; + + return $folder . '/' . ($this->isPage() ? $this->name() : 'default.md'); + } + + /** + * Gets the relative path to the .md file + * + * @return string|null The relative file path + */ + public function filePathClean(): ?string + { + $folder = $this->getStorageFolder(); + if (!$folder) { + return null; + } + + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + $folder = $locator->isStream($folder) ? $locator->getResource($folder, false) : $folder; + + return $folder . '/' . ($this->isPage() ? $this->name() : 'default.md'); + } + + /** + * Gets and sets the order by which any sub-pages should be sorted. + * + * @param string|null $var the order, either "asc" or "desc" + * @return string the order, either "asc" or "desc" + */ + public function orderDir($var = null): string + { + return $this->loadHeaderProperty( + 'order_dir', + $var, + static function ($value) { + return strtolower(trim($value) ?: Grav::instance()['config']->get('system.pages.order.dir')) === 'desc' ? 'desc' : 'asc'; + } + ); + } + + /** + * 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|null $var supported options include "default", "title", "date", and "folder" + * @return string supported options include "default", "title", "date", and "folder" + */ + public function orderBy($var = null): string + { + return $this->loadHeaderProperty( + 'order_by', + $var, + static function ($value) { + return trim($value) ?: Grav::instance()['config']->get('system.pages.order.by'); + } + ); + } + + /** + * Gets the manual order set in the header. + * + * @param string|null $var supported options include "default", "title", "date", and "folder" + * @return array + */ + public function orderManual($var = null): array + { + return $this->loadHeaderProperty( + 'order_manual', + $var, + static function ($value) { + return (array)$value; + } + ); + } + + /** + * 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|null $var the maximum number of sub-pages + * @return int the maximum number of sub-pages + */ + public function maxCount($var = null): int + { + return $this->loadHeaderProperty( + 'max_count', + $var, + static function ($value) { + return (int)($value ?? Grav::instance()['config']->get('system.pages.list.count')); + } + ); + } + + /** + * Gets and sets the modular var that helps identify this page is a modular child + * + * @param bool|null $var true if modular_twig + * @return bool true if modular_twig + * @deprecated 1.7 Use ->isModule() or ->modularTwig() method instead. + */ + public function modular($var = null): bool + { + user_error(__METHOD__ . '() is deprecated since Grav 1.7, use ->isModule() or ->modularTwig() method instead', E_USER_DEPRECATED); + + return $this->modularTwig($var); + } + + /** + * Gets and sets the modular_twig var that helps identify this page as a modular child page that will need + * twig processing handled differently from a regular page. + * + * @param bool|null $var true if modular_twig + * @return bool true if modular_twig + */ + public function modularTwig($var = null): bool + { + if ($var !== null) { + $this->setProperty('modular_twig', (bool)$var); + if ($var) { + $this->visible(false); + } + } + + return (bool)($this->getProperty('modular_twig') ?? strpos($this->slug(), '_') === 0); + } + + /** + * Returns children of this page. + * + * @return PageCollectionInterface|FlexIndexInterface + */ + public function children() + { + $meta = $this->getMetaData(); + $keys = array_keys($meta['children'] ?? []); + $prefix = $this->getMasterKey(); + if ($prefix) { + foreach ($keys as &$key) { + $key = $prefix . '/' . $key; + } + unset($key); + } + + return $this->getFlexDirectory()->getIndex($keys, 'storage_key'); + } + + /** + * Check to see if this item is the first in an array of sub-pages. + * + * @return bool True if item is first. + */ + public function isFirst(): bool + { + $parent = $this->parent(); + $children = $parent ? $parent->children() : null; + if ($children instanceof FlexCollectionInterface) { + $children = $children->withKeyField(); + } + + return $children instanceof PageCollectionInterface ? $children->isFirst($this->getKey()) : true; + } + + /** + * Check to see if this item is the last in an array of sub-pages. + * + * @return bool True if item is last + */ + public function isLast(): bool + { + $parent = $this->parent(); + $children = $parent ? $parent->children() : null; + if ($children instanceof FlexCollectionInterface) { + $children = $children->withKeyField(); + } + + return $children instanceof PageCollectionInterface ? $children->isLast($this->getKey()) : true; + } + + /** + * Gets the previous sibling based on current position. + * + * @return PageInterface|false the previous Page item + */ + public function prevSibling() + { + return $this->adjacentSibling(-1); + } + + /** + * Gets the next sibling based on current position. + * + * @return PageInterface|false the next Page item + */ + public function nextSibling() + { + return $this->adjacentSibling(1); + } + + /** + * Returns the adjacent sibling based on a direction. + * + * @param int $direction either -1 or +1 + * @return PageInterface|false the sibling page + */ + public function adjacentSibling($direction = 1) + { + $parent = $this->parent(); + $children = $parent ? $parent->children() : null; + if ($children instanceof FlexCollectionInterface) { + $children = $children->withKeyField(); + } + + if ($children instanceof PageCollectionInterface) { + $child = $children->adjacentSibling($this->getKey(), $direction); + if ($child instanceof PageInterface) { + return $child; + } + } + + return false; + } + + /** + * Helper method to return an ancestor page. + * + * @param string|null $lookup Name of the parent folder + * @return PageInterface|null page you were looking for if it exists + */ + public function ancestor($lookup = null) + { + /** @var Pages $pages */ + $pages = Grav::instance()['pages']; + + return $pages->ancestor($this->getProperty('parent_route'), $lookup); + } + + /** + * Helper method to return an ancestor page to inherit from. The current + * page object is returned. + * + * @param string $field Name of the parent folder + * @return PageInterface|null + */ + public function inherited($field) + { + [$inherited, $currentParams] = $this->getInheritedParams($field); + + $this->modifyHeader($field, $currentParams); + + return $inherited; + } + + /** + * Helper method to return an ancestor field only to inherit from. The + * first occurrence of an ancestor field will be returned if at all. + * + * @param string $field Name of the parent folder + * @return array + */ + public function inheritedField($field): array + { + [, $currentParams] = $this->getInheritedParams($field); + + return $currentParams; + } + + /** + * Method that contains shared logic for inherited() and inheritedField() + * + * @param string $field Name of the parent folder + * @return array + */ + protected function getInheritedParams($field): array + { + /** @var Pages $pages */ + $pages = Grav::instance()['pages']; + + $inherited = $pages->inherited($this->getProperty('parent_route'), $field); + $inheritedParams = $inherited ? (array)$inherited->value('header.' . $field) : []; + $currentParams = (array)$this->getFormValue('header.' . $field); + if ($inheritedParams && is_array($inheritedParams)) { + $currentParams = array_replace_recursive($inheritedParams, $currentParams); + } + + return [$inherited, $currentParams]; + } + + /** + * Helper method to return a page. + * + * @param string $url the url of the page + * @param bool $all + * @return PageInterface|null page you were looking for if it exists + */ + public function find($url, $all = false) + { + /** @var Pages $pages */ + $pages = Grav::instance()['pages']; + + return $pages->find($url, $all); + } + + /** + * Get a collection of pages in the current context. + * + * @param string|array $params + * @param bool $pagination + * @return PageCollectionInterface|Collection + * @throws InvalidArgumentException + */ + public function collection($params = 'content', $pagination = true) + { + if (is_string($params)) { + // Look into a page header field. + $params = (array)$this->getFormValue('header.' . $params); + } elseif (!is_array($params)) { + throw new InvalidArgumentException('Argument should be either header variable name or array of parameters'); + } + + if (!$pagination) { + $params['pagination'] = false; + } + $context = [ + 'pagination' => $pagination, + 'self' => $this + ]; + + /** @var Pages $pages */ + $pages = Grav::instance()['pages']; + + return $pages->getCollection($params, $context); + } + + /** + * @param string|array $value + * @param bool $only_published + * @return PageCollectionInterface|Collection + */ + public function evaluate($value, $only_published = true) + { + $params = [ + 'items' => $value, + 'published' => $only_published + ]; + $context = [ + 'event' => false, + 'pagination' => false, + 'url_taxonomy_filters' => false, + 'self' => $this + ]; + + /** @var Pages $pages */ + $pages = Grav::instance()['pages']; + + return $pages->getCollection($params, $context); + } + + /** + * Returns whether or not the current folder exists + * + * @return bool + */ + public function folderExists(): bool + { + return $this->exists() || is_dir($this->getStorageFolder() ?? ''); + } + + /** + * Gets the action. + * + * @return string|null The Action string. + */ + public function getAction(): ?string + { + $meta = $this->getMetaData(); + if (!empty($meta['copy'])) { + return 'copy'; + } + if (isset($meta['storage_key']) && $this->getStorageKey() !== $meta['storage_key']) { + return 'move'; + } + + return null; + } +} diff --git a/system/src/Grav/Framework/Flex/Pages/Traits/PageRoutableTrait.php b/system/src/Grav/Framework/Flex/Pages/Traits/PageRoutableTrait.php new file mode 100644 index 0000000..619934e --- /dev/null +++ b/system/src/Grav/Framework/Flex/Pages/Traits/PageRoutableTrait.php @@ -0,0 +1,550 @@ +loadHeaderProperty( + 'url_extension', + null, + function ($value) { + if ($this->home()) { + return ''; + } + + return $value ?? Grav::instance()['config']->get('system.pages.append_url_extension', ''); + } + ); + } + + /** + * Gets and Sets whether or not this Page is routable, ie you can reach it via a URL. + * The page must be *routable* and *published* + * + * @param bool|null $var true if the page is routable + * @return bool true if the page is routable + */ + public function routable($var = null): bool + { + $value = $this->loadHeaderProperty( + 'routable', + $var, + static function ($value) { + return $value ?? true; + } + ); + + return $value && $this->published() && !$this->isModule() && !$this->root() && $this->getLanguages(true); + } + + /** + * Gets the URL for a page - alias of url(). + * + * @param bool $include_host + * @return string the permalink + */ + public function link($include_host = false): string + { + return $this->url($include_host); + } + + /** + * Gets the URL with host information, aka Permalink. + * @return string The permalink. + */ + public function permalink(): string + { + return $this->url(true, false, true, true); + } + + /** + * Returns the canonical URL for a page + * + * @param bool $include_lang + * @return string + */ + public function canonical($include_lang = true): string + { + return $this->url(true, true, $include_lang); + } + + /** + * Gets the url for the Page. + * + * @param bool $include_host Defaults false, but true would include http://yourhost.com + * @param bool $canonical true to return the canonical URL + * @param bool $include_base + * @param bool $raw_route + * @return string The url. + */ + public function url($include_host = false, $canonical = false, $include_base = true, $raw_route = false): string + { + // Override any URL when external_url is set + $external = $this->getNestedProperty('header.external_url'); + if ($external) { + return $external; + } + + $grav = Grav::instance(); + + /** @var Pages $pages */ + $pages = $grav['pages']; + + /** @var Config $config */ + $config = $grav['config']; + + // get base route (multi-site base and language) + $route = $include_base ? $pages->baseRoute() : ''; + + // add full route if configured to do so + if (!$include_host && $config->get('system.absolute_urls', false)) { + $include_host = true; + } + + if ($canonical) { + $route .= $this->routeCanonical(); + } elseif ($raw_route) { + $route .= $this->rawRoute(); + } else { + $route .= $this->route(); + } + + /** @var Uri $uri */ + $uri = $grav['uri']; + $url = $uri->rootUrl($include_host) . '/' . trim($route, '/') . $this->urlExtension(); + + return Uri::filterPath($url); + } + + /** + * Gets the route for the page based on the route headers if available, else from + * the parents route and the current Page's slug. + * + * @param string $var Set new default route. + * @return string|null The route for the Page. + */ + public function route($var = null): ?string + { + if (null !== $var) { + // TODO: not the best approach, but works... + $this->setNestedProperty('header.routes.default', $var); + } + + // Return default route if given. + $default = $this->getNestedProperty('header.routes.default'); + if (is_string($default)) { + return $default; + } + + return $this->routeInternal(); + } + + /** + * @return string|null + */ + protected function routeInternal(): ?string + { + $route = $this->_route; + if (null !== $route) { + return $route; + } + + if ($this->root()) { + return null; + } + + // Root and orphan nodes have no route. + $parent = $this->parent(); + if (!$parent) { + return null; + } + + if ($parent->home()) { + /** @var Config $config */ + $config = Grav::instance()['config']; + $hide = (bool)$config->get('system.home.hide_in_urls', false); + $route = '/' . ($hide ? '' : $parent->slug()); + } else { + $route = $parent->route(); + } + + if ($route !== '' && $route !== '/') { + $route .= '/'; + } + + if (!$this->home()) { + $route .= $this->slug(); + } + + $this->_route = $route; + + return $route; + } + + /** + * Helper method to clear the route out so it regenerates next time you use it + */ + public function unsetRouteSlug(): void + { + // TODO: + throw new RuntimeException(__METHOD__ . '(): Not Implemented'); + } + + /** + * Gets and Sets the page raw route + * + * @param string|null $var + * @return string|null + */ + public function rawRoute($var = null): ?string + { + if (null !== $var) { + // TODO: + throw new RuntimeException(__METHOD__ . '(string): Not Implemented'); + } + + if ($this->root()) { + return null; + } + + return '/' . $this->getKey(); + } + + /** + * Gets the route aliases for the page based on page headers. + * + * @param array|null $var list of route aliases + * @return array The route aliases for the Page. + */ + public function routeAliases($var = null): array + { + if (null !== $var) { + $this->setNestedProperty('header.routes.aliases', (array)$var); + } + + $aliases = (array)$this->getNestedProperty('header.routes.aliases'); + $default = $this->getNestedProperty('header.routes.default'); + if ($default) { + $aliases[] = $default; + } + + return $aliases; + } + + /** + * Gets the canonical route for this page if its set. If provided it will use + * that value, else if it's `true` it will use the default route. + * + * @param string|null $var + * @return string|null + */ + public function routeCanonical($var = null): ?string + { + if (null !== $var) { + $this->setNestedProperty('header.routes.canonical', (array)$var); + } + + $canonical = $this->getNestedProperty('header.routes.canonical'); + + return is_string($canonical) ? $canonical : $this->route(); + } + + /** + * Gets the redirect set in the header. + * + * @param string|null $var redirect url + * @return string|null + */ + public function redirect($var = null): ?string + { + return $this->loadHeaderProperty( + 'redirect', + $var, + static function ($value) { + return trim($value) ?: null; + } + ); + } + + /** + * Returns the clean path to the page file + * + * Needed in admin for Page Media. + */ + public function relativePagePath(): ?string + { + $folder = $this->getMediaFolder(); + if (!$folder) { + return null; + } + + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + $path = $locator->isStream($folder) ? $locator->findResource($folder, false) : $folder; + + return is_string($path) ? $path : 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|null $var the path + * @return string|null the path + */ + public function path($var = null): ?string + { + if (null !== $var) { + // TODO: + throw new RuntimeException(__METHOD__ . '(string): Not Implemented'); + } + + $path = $this->_path; + if ($path) { + return $path; + } + + if ($this->root()) { + $folder = $this->getFlexDirectory()->getStorageFolder(); + } else { + $folder = $this->getStorageFolder(); + } + + if ($folder) { + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + $folder = $locator->isStream($folder) ? $locator->getResource($folder) : GRAV_ROOT . "/{$folder}"; + } + + return $this->_path = is_string($folder) ? $folder : null; + } + + /** + * Get/set the folder. + * + * @param string|null $var Optional path, including numeric prefix. + * @return string|null + */ + public function folder($var = null): ?string + { + return $this->loadProperty( + 'folder', + $var, + function ($value) { + if (null === $value) { + $value = $this->getMasterKey() ?: $this->getKey(); + } + + return Utils::basename($value) ?: null; + } + ); + } + + /** + * Get/set the folder. + * + * @param string|null $var Optional path, including numeric prefix. + * @return string|null + */ + public function parentStorageKey($var = null): ?string + { + return $this->loadProperty( + 'parent_key', + $var, + function ($value) { + if (null === $value) { + $filesystem = Filesystem::getInstance(false); + $value = $this->getMasterKey() ?: $this->getKey(); + $value = ltrim($filesystem->dirname("/{$value}"), '/') ?: ''; + } + + return $value; + } + ); + } + + /** + * Gets and Sets the parent object for this page + * + * @param PageInterface|null $var the parent page object + * @return PageInterface|null the parent page object if it exists. + */ + public function parent(PageInterface $var = null) + { + if (null !== $var) { + // TODO: + throw new RuntimeException(__METHOD__ . '(PageInterface): Not Implemented'); + } + + if ($this->_parentCache || $this->root()) { + return $this->_parentCache; + } + + // Use filesystem as \dirname() does not work in Windows because of '/foo' becomes '\'. + $filesystem = Filesystem::getInstance(false); + $directory = $this->getFlexDirectory(); + $parentKey = ltrim($filesystem->dirname("/{$this->getKey()}"), '/'); + if ('' !== $parentKey) { + $parent = $directory->getObject($parentKey); + $language = $this->getLanguage(); + if ($language && $parent && method_exists($parent, 'getTranslation')) { + $parent = $parent->getTranslation($language) ?? $parent; + } + + $this->_parentCache = $parent; + } else { + $index = $directory->getIndex(); + + $this->_parentCache = \is_callable([$index, 'getRoot']) ? $index->getRoot() : null; + } + + return $this->_parentCache; + } + + /** + * Gets the top parent object for this page. Can return page itself. + * + * @return PageInterface The top parent page object. + */ + public function topParent() + { + $topParent = $this; + while ($topParent) { + $parent = $topParent->parent(); + if (!$parent || !$parent->parent()) { + break; + } + $topParent = $parent; + } + + return $topParent; + } + + /** + * Returns the item in the current position. + * + * @return int|null the index of the current page. + */ + public function currentPosition(): ?int + { + $parent = $this->parent(); + $collection = $parent ? $parent->collection('content', false) : null; + if ($collection instanceof PageCollectionInterface && $path = $this->path()) { + return $collection->currentPosition($path); + } + + return 1; + } + + /** + * 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(): bool + { + $grav = Grav::instance(); + $uri_path = rtrim(urldecode($grav['uri']->path()), '/') ?: '/'; + $routes = $grav['pages']->routes(); + + return isset($routes[$uri_path]) && $routes[$uri_path] === $this->path(); + } + + /** + * 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(): bool + { + $grav = Grav::instance(); + /** @var Uri $uri */ + $uri = $grav['uri']; + /** @var Pages $pages */ + $pages = $grav['pages']; + $uri_path = rtrim(urldecode($uri->path()), '/'); + $routes = $pages->routes(); + + if (isset($routes[$uri_path])) { + $page = $pages->find($uri->route()); + /** @var PageInterface|null $child_page */ + $child_page = $page ? $page->parent() : null; + while ($child_page && !$child_page->root()) { + if ($this->path() === $child_page->path()) { + return true; + } + $child_page = $child_page->parent(); + } + } + + return false; + } + + /** + * Returns whether or not this page is the currently configured home page. + * + * @return bool True if it is the homepage + */ + public function home(): bool + { + $home = Grav::instance()['config']->get('system.home.alias'); + + return '/' . $this->getKey() === $home; + } + + /** + * Returns whether or not this page is the root node of the pages tree. + * + * @param bool|null $var + * @return bool True if it is the root + */ + public function root($var = null): bool + { + if (null !== $var) { + $this->root = (bool)$var; + } + + return $this->root === true || $this->getKey() === '/'; + } +} diff --git a/system/src/Grav/Framework/Flex/Pages/Traits/PageTranslateTrait.php b/system/src/Grav/Framework/Flex/Pages/Traits/PageTranslateTrait.php new file mode 100644 index 0000000..7af1b4f --- /dev/null +++ b/system/src/Grav/Framework/Flex/Pages/Traits/PageTranslateTrait.php @@ -0,0 +1,291 @@ +translatedLanguages(true); + } + + /** + * @param string|null $languageCode + * @param bool|null $fallback + * @return bool + */ + public function hasTranslation(string $languageCode = null, bool $fallback = null): bool + { + $code = $this->findTranslation($languageCode, $fallback); + + return null !== $code; + } + + /** + * @param string|null $languageCode + * @param bool|null $fallback + * @return FlexObjectInterface|PageInterface|null + */ + public function getTranslation(string $languageCode = null, bool $fallback = null) + { + if ($this->root()) { + return $this; + } + + $code = $this->findTranslation($languageCode, $fallback); + if (null === $code) { + $object = null; + } elseif ('' === $code) { + $object = $this->getLanguage() ? $this->getFlexDirectory()->getObject($this->getMasterKey(), 'storage_key') : $this; + } else { + $meta = $this->getMetaData(); + $meta['template'] = $this->getLanguageTemplates()[$code] ?? $meta['template']; + $key = $this->getStorageKey() . '|' . $meta['template'] . '.' . $code; + $meta['storage_key'] = $key; + $meta['lang'] = $code; + $object = $this->getFlexDirectory()->loadObjects([$key => $meta])[$key] ?? null; + } + + return $object; + } + + /** + * @param bool $includeDefault If set to true, return separate entries for '' and 'en' (default) language. + * @return array + */ + public function getAllLanguages(bool $includeDefault = false): array + { + $grav = Grav::instance(); + + /** @var Language $language */ + $language = $grav['language']; + $languages = $language->getLanguages(); + if (!$languages) { + return []; + } + + $translated = $this->getLanguageTemplates(); + + if ($includeDefault) { + $languages[] = ''; + } elseif (isset($translated[''])) { + $default = $language->getDefault(); + if (is_bool($default)) { + $default = ''; + } + $translated[$default] = $translated['']; + unset($translated['']); + } + + $languages = array_fill_keys($languages, false); + $translated = array_fill_keys(array_keys($translated), true); + + return array_replace($languages, $translated); + } + + /** + * Returns all translated languages. + * + * @param bool $includeDefault If set to true, return separate entries for '' and 'en' (default) language. + * @return array + */ + public function getLanguages(bool $includeDefault = false): array + { + $languages = $this->getLanguageTemplates(); + + if (!$includeDefault && isset($languages[''])) { + $grav = Grav::instance(); + + /** @var Language $language */ + $language = $grav['language']; + $default = $language->getDefault(); + if (is_bool($default)) { + $default = ''; + } + $languages[$default] = $languages['']; + unset($languages['']); + } + + return array_keys($languages); + } + + /** + * @return string + */ + public function getLanguage(): string + { + return $this->language() ?? ''; + } + + /** + * @param string|null $languageCode + * @param bool|null $fallback + * @return string|null + */ + public function findTranslation(string $languageCode = null, bool $fallback = null): ?string + { + $translated = $this->getLanguageTemplates(); + + // If there's no translations (including default), we have an empty folder. + if (!$translated) { + return ''; + } + + // FIXME: only published is not implemented... + $languages = $this->getFallbackLanguages($languageCode, $fallback); + + $language = null; + foreach ($languages as $code) { + if (isset($translated[$code])) { + $language = $code; + break; + } + } + + return $language; + } + + /** + * Return an array with the routes of other translated languages + * + * @param bool $onlyPublished only return published translations + * @return array the page translated languages + */ + public function translatedLanguages($onlyPublished = false): array + { + // FIXME: only published is not implemented... + $translated = $this->getLanguageTemplates(); + if (!$translated) { + return $translated; + } + + $grav = Grav::instance(); + + /** @var Language $language */ + $language = $grav['language']; + $languages = $language->getLanguages(); + $languages[] = ''; + + $translated = array_intersect_key($translated, array_flip($languages)); + $list = array_fill_keys($languages, null); + foreach ($translated as $languageCode => $languageFile) { + $path = ($languageCode ? '/' : '') . $languageCode; + $list[$languageCode] = "{$path}/{$this->getKey()}"; + } + + return array_filter($list); + } + + /** + * Return an array listing untranslated languages available + * + * @param bool $includeUnpublished also list unpublished translations + * @return array the page untranslated languages + */ + public function untranslatedLanguages($includeUnpublished = false): array + { + $grav = Grav::instance(); + + /** @var Language $language */ + $language = $grav['language']; + + $languages = $language->getLanguages(); + $translated = array_keys($this->translatedLanguages(!$includeUnpublished)); + + return array_values(array_diff($languages, $translated)); + } + + /** + * Get page language + * + * @param string|null $var + * @return string|null + */ + public function language($var = null): ?string + { + return $this->loadHeaderProperty( + 'lang', + $var, + function ($value) { + $value = $value ?? $this->getMetaData()['lang'] ?? ''; + + return trim($value) ?: null; + } + ); + } + + /** + * @return array + */ + protected function getLanguageTemplates(): array + { + if (null === $this->_languages) { + $template = $this->getProperty('template'); + $meta = $this->getMetaData(); + $translations = $meta['markdown'] ?? []; + $list = []; + foreach ($translations as $code => $search) { + if (isset($search[$template])) { + // Use main template if possible. + $list[$code] = $template; + } elseif (!empty($search)) { + // Fall back to first matching template. + $list[$code] = key($search); + } + } + + $this->_languages = $list; + } + + return $this->_languages; + } + + /** + * @param string|null $languageCode + * @param bool|null $fallback + * @return array + */ + protected function getFallbackLanguages(string $languageCode = null, bool $fallback = null): array + { + $fallback = $fallback ?? true; + if (!$fallback && null !== $languageCode) { + return [$languageCode]; + } + + $grav = Grav::instance(); + + /** @var Language $language */ + $language = $grav['language']; + $languageCode = $languageCode ?? ($language->getLanguage() ?: ''); + if ($languageCode === '' && $fallback) { + return $language->getFallbackLanguages(null, true); + } + + return $fallback ? $language->getFallbackLanguages($languageCode, true) : [$languageCode]; + } +} diff --git a/system/src/Grav/Framework/Flex/Storage/AbstractFilesystemStorage.php b/system/src/Grav/Framework/Flex/Storage/AbstractFilesystemStorage.php new file mode 100644 index 0000000..e02ef53 --- /dev/null +++ b/system/src/Grav/Framework/Flex/Storage/AbstractFilesystemStorage.php @@ -0,0 +1,232 @@ +hasKey((string)$key); + } + + return $list; + } + + /** + * {@inheritDoc} + * @see FlexStorageInterface::getKeyField() + */ + public function getKeyField(): string + { + return $this->keyField; + } + + /** + * @param array $keys + * @param bool $includeParams + * @return string + */ + public function buildStorageKey(array $keys, bool $includeParams = true): string + { + $key = $keys['key'] ?? ''; + $params = $includeParams ? $this->buildStorageKeyParams($keys) : ''; + + return $params ? "{$key}|{$params}" : $key; + } + + /** + * @param array $keys + * @return string + */ + public function buildStorageKeyParams(array $keys): string + { + return ''; + } + + /** + * @param array $row + * @return array + */ + public function extractKeysFromRow(array $row): array + { + return [ + 'key' => $this->normalizeKey($row[$this->keyField] ?? '') + ]; + } + + /** + * @param string $key + * @return array + */ + public function extractKeysFromStorageKey(string $key): array + { + return [ + 'key' => $key + ]; + } + + /** + * @param string|array $formatter + * @return void + */ + protected function initDataFormatter($formatter): void + { + // Initialize formatter. + if (!is_array($formatter)) { + $formatter = ['class' => $formatter]; + } + $formatterClassName = $formatter['class'] ?? JsonFormatter::class; + $formatterOptions = $formatter['options'] ?? []; + + if (!is_a($formatterClassName, FileFormatterInterface::class, true)) { + throw new \InvalidArgumentException('Bad Data Formatter'); + } + + $this->dataFormatter = new $formatterClassName($formatterOptions); + } + + /** + * @param string $filename + * @return string|null + */ + protected function detectDataFormatter(string $filename): ?string + { + if (preg_match('|(\.[a-z0-9]*)$|ui', $filename, $matches)) { + switch ($matches[1]) { + case '.json': + return JsonFormatter::class; + case '.yaml': + return YamlFormatter::class; + case '.md': + return MarkdownFormatter::class; + } + } + + return null; + } + + /** + * @param string $filename + * @return CompiledJsonFile|CompiledYamlFile|CompiledMarkdownFile + */ + protected function getFile(string $filename) + { + $filename = $this->resolvePath($filename); + + // TODO: start using the new file classes. + switch ($this->dataFormatter->getDefaultFileExtension()) { + case '.json': + $file = CompiledJsonFile::instance($filename); + break; + case '.yaml': + $file = CompiledYamlFile::instance($filename); + break; + case '.md': + $file = CompiledMarkdownFile::instance($filename); + break; + default: + throw new RuntimeException('Unknown extension type ' . $this->dataFormatter->getDefaultFileExtension()); + } + + return $file; + } + + /** + * @param string $path + * @return string + */ + protected function resolvePath(string $path): string + { + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + + if (!$locator->isStream($path)) { + return GRAV_ROOT . "/{$path}"; + } + + return $locator->getResource($path); + } + + /** + * Generates a random, unique key for the row. + * + * @return string + */ + protected function generateKey(): string + { + return substr(hash('sha256', random_bytes($this->keyLen)), 0, $this->keyLen); + } + + /** + * @param string $key + * @return string + */ + public function normalizeKey(string $key): string + { + if ($this->caseSensitive === true) { + return $key; + } + + return mb_strtolower($key); + } + + /** + * Checks if a key is valid. + * + * @param string $key + * @return bool + */ + protected function validateKey(string $key): bool + { + return $key && (bool) preg_match('/^[^\\/?*:;{}\\\\\\n]+$/u', $key); + } +} diff --git a/system/src/Grav/Framework/Flex/Storage/FileStorage.php b/system/src/Grav/Framework/Flex/Storage/FileStorage.php new file mode 100644 index 0000000..1fe8c18 --- /dev/null +++ b/system/src/Grav/Framework/Flex/Storage/FileStorage.php @@ -0,0 +1,160 @@ +dataPattern = '{FOLDER}/{KEY}{EXT}'; + + if (!isset($options['formatter']) && isset($options['pattern'])) { + $options['formatter'] = $this->detectDataFormatter($options['pattern']); + } + + parent::__construct($options); + } + + /** + * {@inheritdoc} + * @see FlexStorageInterface::getMediaPath() + */ + public function getMediaPath(string $key = null): ?string + { + $path = $this->getStoragePath(); + if (!$path) { + return null; + } + + return $key ? "{$path}/{$key}" : $path; + } + + /** + * @param string $src + * @param string $dst + * @return bool + */ + public function copyRow(string $src, string $dst): bool + { + if ($this->hasKey($dst)) { + throw new RuntimeException("Cannot copy object: key '{$dst}' is already taken"); + } + + if (!$this->hasKey($src)) { + return false; + } + + return true; + } + + /** + * {@inheritdoc} + * @see FlexStorageInterface::renameRow() + */ + public function renameRow(string $src, string $dst): bool + { + if (!$this->hasKey($src)) { + return false; + } + + // Remove old file. + $path = $this->getPathFromKey($src); + $file = $this->getFile($path); + $file->delete(); + $file->free(); + unset($file); + + return true; + } + + /** + * @param string $src + * @param string $dst + * @return bool + */ + protected function copyFolder(string $src, string $dst): bool + { + // Nothing to copy. + return true; + } + + /** + * @param string $src + * @param string $dst + * @return bool + */ + protected function moveFolder(string $src, string $dst): bool + { + // Nothing to move. + return true; + } + + /** + * @param string $key + * @return bool + */ + protected function canDeleteFolder(string $key): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + protected function getKeyFromPath(string $path): string + { + return Utils::basename($path, $this->dataFormatter->getDefaultFileExtension()); + } + + /** + * {@inheritdoc} + */ + protected function buildIndex(): array + { + $this->clearCache(); + + $path = $this->getStoragePath(); + if (!$path || !file_exists($path)) { + return []; + } + + $flags = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS; + $iterator = new FilesystemIterator($path, $flags); + $list = []; + /** @var SplFileInfo $info */ + foreach ($iterator as $filename => $info) { + if (!$info->isFile() || !($key = $this->getKeyFromPath($filename)) || strpos($info->getFilename(), '.') === 0) { + continue; + } + + $list[$key] = $this->getObjectMeta($key); + } + + ksort($list, SORT_NATURAL | SORT_FLAG_CASE); + + return $list; + } +} diff --git a/system/src/Grav/Framework/Flex/Storage/FolderStorage.php b/system/src/Grav/Framework/Flex/Storage/FolderStorage.php new file mode 100644 index 0000000..6f03d9d --- /dev/null +++ b/system/src/Grav/Framework/Flex/Storage/FolderStorage.php @@ -0,0 +1,708 @@ + '%1$s', 'KEY' => '%2$s', 'KEY:2' => '%3$s', 'FILE' => '%4$s', 'EXT' => '%5$s']; + /** @var string Filename for the object. */ + protected $dataFile; + /** @var string File extension for the object. */ + protected $dataExt; + /** @var bool */ + protected $prefixed; + /** @var bool */ + protected $indexed; + /** @var array */ + protected $meta = []; + + /** + * {@inheritdoc} + */ + public function __construct(array $options) + { + if (!isset($options['folder'])) { + throw new InvalidArgumentException("Argument \$options is missing 'folder'"); + } + + $this->initDataFormatter($options['formatter'] ?? []); + $this->initOptions($options); + } + + /** + * @return bool + */ + public function isIndexed(): bool + { + return $this->indexed; + } + + /** + * @return void + */ + public function clearCache(): void + { + $this->meta = []; + } + + /** + * @param string[] $keys + * @param bool $reload + * @return array + */ + public function getMetaData(array $keys, bool $reload = false): array + { + $list = []; + foreach ($keys as $key) { + $list[$key] = $this->getObjectMeta((string)$key, $reload); + } + + return $list; + } + + /** + * {@inheritdoc} + * @see FlexStorageInterface::getExistingKeys() + */ + public function getExistingKeys(): array + { + return $this->buildIndex(); + } + + /** + * {@inheritdoc} + * @see FlexStorageInterface::hasKey() + */ + public function hasKey(string $key): bool + { + $meta = $this->getObjectMeta($key); + + return array_key_exists('exists', $meta) ? $meta['exists'] : !empty($meta['storage_timestamp']); + } + + /** + * {@inheritdoc} + * @see FlexStorageInterface::createRows() + */ + public function createRows(array $rows): array + { + $list = []; + foreach ($rows as $key => $row) { + $list[$key] = $this->saveRow('@@', $row); + } + + return $list; + } + + /** + * {@inheritdoc} + * @see FlexStorageInterface::readRows() + */ + public function readRows(array $rows, array &$fetched = null): array + { + $list = []; + foreach ($rows as $key => $row) { + if (null === $row || is_scalar($row)) { + // Only load rows which haven't been loaded before. + $key = (string)$key; + $list[$key] = $this->loadRow($key); + + if (null !== $fetched) { + $fetched[$key] = $list[$key]; + } + } else { + // Keep the row if it has been loaded. + $list[$key] = $row; + } + } + + return $list; + } + + /** + * {@inheritdoc} + * @see FlexStorageInterface::updateRows() + */ + public function updateRows(array $rows): array + { + $list = []; + foreach ($rows as $key => $row) { + $key = (string)$key; + $list[$key] = $this->hasKey($key) ? $this->saveRow($key, $row) : null; + } + + return $list; + } + + /** + * {@inheritdoc} + * @see FlexStorageInterface::deleteRows() + */ + public function deleteRows(array $rows): array + { + $list = []; + $baseMediaPath = $this->getMediaPath(); + foreach ($rows as $key => $row) { + $key = (string)$key; + if (!$this->hasKey($key)) { + $list[$key] = null; + } else { + $path = $this->getPathFromKey($key); + $file = $this->getFile($path); + $list[$key] = $this->deleteFile($file); + + if ($this->canDeleteFolder($key)) { + $storagePath = $this->getStoragePath($key); + $mediaPath = $this->getMediaPath($key); + + if ($storagePath) { + $this->deleteFolder($storagePath, true); + } + if ($mediaPath && $mediaPath !== $storagePath && $mediaPath !== $baseMediaPath) { + $this->deleteFolder($mediaPath, true); + } + } + } + } + + return $list; + } + + /** + * {@inheritdoc} + * @see FlexStorageInterface::replaceRows() + */ + public function replaceRows(array $rows): array + { + $list = []; + foreach ($rows as $key => $row) { + $key = (string)$key; + $list[$key] = $this->saveRow($key, $row); + } + + return $list; + } + + /** + * @param string $src + * @param string $dst + * @return bool + * @throws RuntimeException + */ + public function copyRow(string $src, string $dst): bool + { + if ($this->hasKey($dst)) { + throw new RuntimeException("Cannot copy object: key '{$dst}' is already taken"); + } + + if (!$this->hasKey($src)) { + return false; + } + + $srcPath = $this->getStoragePath($src); + $dstPath = $this->getStoragePath($dst); + if (!$srcPath || !$dstPath) { + return false; + } + + return $this->copyFolder($srcPath, $dstPath); + } + + /** + * {@inheritdoc} + * @see FlexStorageInterface::renameRow() + * @throws RuntimeException + */ + public function renameRow(string $src, string $dst): bool + { + if (!$this->hasKey($src)) { + return false; + } + + $srcPath = $this->getStoragePath($src); + $dstPath = $this->getStoragePath($dst); + if (!$srcPath || !$dstPath) { + throw new RuntimeException("Destination path '{$dst}' is empty"); + } + + if ($srcPath === $dstPath) { + return true; + } + + if ($this->hasKey($dst)) { + throw new RuntimeException("Cannot rename object '{$src}': key '{$dst}' is already taken $srcPath $dstPath"); + } + + return $this->moveFolder($srcPath, $dstPath); + } + + /** + * {@inheritdoc} + * @see FlexStorageInterface::getStoragePath() + */ + public function getStoragePath(string $key = null): ?string + { + if (null === $key || $key === '') { + $path = $this->dataFolder; + } else { + $parts = $this->parseKey($key, false); + $options = [ + $this->dataFolder, // {FOLDER} + $parts['key'], // {KEY} + $parts['key:2'], // {KEY:2} + '***', // {FILE} + '***' // {EXT} + ]; + + $path = rtrim(explode('***', sprintf($this->dataPattern, ...$options))[0], '/'); + } + + return $path; + } + + /** + * {@inheritdoc} + * @see FlexStorageInterface::getMediaPath() + */ + public function getMediaPath(string $key = null): ?string + { + return $this->getStoragePath($key); + } + + /** + * Get filesystem path from the key. + * + * @param string $key + * @return string + */ + public function getPathFromKey(string $key): string + { + $parts = $this->parseKey($key); + $options = [ + $this->dataFolder, // {FOLDER} + $parts['key'], // {KEY} + $parts['key:2'], // {KEY:2} + $parts['file'], // {FILE} + $this->dataExt // {EXT} + ]; + + return sprintf($this->dataPattern, ...$options); + } + + /** + * @param string $key + * @param bool $variations + * @return array + */ + public function parseKey(string $key, bool $variations = true): array + { + $keys = [ + 'key' => $key, + 'key:2' => mb_substr($key, 0, 2), + ]; + if ($variations) { + $keys['file'] = $this->dataFile; + } + + return $keys; + } + + /** + * Get key from the filesystem path. + * + * @param string $path + * @return string + */ + protected function getKeyFromPath(string $path): string + { + return Utils::basename($path); + } + + /** + * Prepares the row for saving and returns the storage key for the record. + * + * @param array $row + * @return void + */ + protected function prepareRow(array &$row): void + { + if (array_key_exists($this->keyField, $row)) { + $key = $row[$this->keyField]; + if ($key === $this->normalizeKey($key)) { + unset($row[$this->keyField]); + } + } + } + + /** + * @param string $key + * @return array + */ + protected function loadRow(string $key): ?array + { + $path = $this->getPathFromKey($key); + $file = $this->getFile($path); + try { + $data = (array)$file->content(); + if (isset($data[0])) { + throw new RuntimeException('Broken object file'); + } + + // Add key field to the object. + $keyField = $this->keyField; + if ($keyField !== 'storage_key' && !isset($data[$keyField])) { + $data[$keyField] = $key; + } + } catch (RuntimeException $e) { + $data = ['__ERROR' => $e->getMessage()]; + } finally { + $file->free(); + unset($file); + } + + $data['__META'] = $this->getObjectMeta($key); + + return $data; + } + + /** + * @param string $key + * @param array $row + * @return array + */ + protected function saveRow(string $key, array $row): array + { + try { + if (isset($row[$this->keyField])) { + $key = $row[$this->keyField]; + } + if (strpos($key, '@@') !== false) { + $key = $this->getNewKey(); + } + + $key = $this->normalizeKey($key); + + // Check if the row already exists and if the key has been changed. + $oldKey = $row['__META']['storage_key'] ?? null; + if (is_string($oldKey) && $oldKey !== $key) { + $isCopy = $row['__META']['copy'] ?? false; + if ($isCopy) { + $this->copyRow($oldKey, $key); + } else { + $this->renameRow($oldKey, $key); + } + } + + $this->prepareRow($row); + unset($row['__META'], $row['__ERROR']); + + $path = $this->getPathFromKey($key); + $file = $this->getFile($path); + + $file->save($row); + + } catch (RuntimeException $e) { + throw new RuntimeException(sprintf('Flex saveFile(%s): %s', $path ?? $key, $e->getMessage())); + } finally { + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + $locator->clearCache(); + + if (isset($file)) { + $file->free(); + unset($file); + } + } + + $row['__META'] = $this->getObjectMeta($key, true); + + return $row; + } + + /** + * @param File $file + * @return array|string + */ + protected function deleteFile(File $file) + { + $filename = $file->filename(); + try { + $data = $file->content(); + if ($file->exists()) { + $file->delete(); + } + } catch (RuntimeException $e) { + throw new RuntimeException(sprintf('Flex deleteFile(%s): %s', $filename, $e->getMessage())); + } finally { + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + $locator->clearCache(); + + $file->free(); + } + + return $data; + } + + /** + * @param string $src + * @param string $dst + * @return bool + */ + protected function copyFolder(string $src, string $dst): bool + { + try { + Folder::copy($this->resolvePath($src), $this->resolvePath($dst)); + } catch (RuntimeException $e) { + throw new RuntimeException(sprintf('Flex copyFolder(%s, %s): %s', $src, $dst, $e->getMessage())); + } finally { + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + $locator->clearCache(); + } + + return true; + } + + /** + * @param string $src + * @param string $dst + * @return bool + */ + protected function moveFolder(string $src, string $dst): bool + { + try { + Folder::move($this->resolvePath($src), $this->resolvePath($dst)); + } catch (RuntimeException $e) { + throw new RuntimeException(sprintf('Flex moveFolder(%s, %s): %s', $src, $dst, $e->getMessage())); + } finally { + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + $locator->clearCache(); + } + + return true; + } + + /** + * @param string $path + * @param bool $include_target + * @return bool + */ + protected function deleteFolder(string $path, bool $include_target = false): bool + { + try { + return Folder::delete($this->resolvePath($path), $include_target); + } catch (RuntimeException $e) { + throw new RuntimeException(sprintf('Flex deleteFolder(%s): %s', $path, $e->getMessage())); + } finally { + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + $locator->clearCache(); + } + } + + /** + * @param string $key + * @return bool + */ + protected function canDeleteFolder(string $key): bool + { + return true; + } + + /** + * Returns list of all stored keys in [key => timestamp] pairs. + * + * @return array + */ + protected function buildIndex(): array + { + $this->clearCache(); + + $path = $this->getStoragePath(); + if (!$path || !file_exists($path)) { + return []; + } + + if ($this->prefixed) { + $list = $this->buildPrefixedIndexFromFilesystem($path); + } else { + $list = $this->buildIndexFromFilesystem($path); + } + + ksort($list, SORT_NATURAL | SORT_FLAG_CASE); + + return $list; + } + + /** + * @param string $key + * @param bool $reload + * @return array + */ + protected function getObjectMeta(string $key, bool $reload = false): array + { + if (!$reload && isset($this->meta[$key])) { + return $this->meta[$key]; + } + + if ($key && strpos($key, '@@') === false) { + $filename = $this->getPathFromKey($key); + $modified = is_file($filename) ? filemtime($filename) : 0; + } else { + $modified = 0; + } + + $meta = [ + 'storage_key' => $key, + 'storage_timestamp' => $modified + ]; + + $this->meta[$key] = $meta; + + return $meta; + } + + /** + * @param string $path + * @return array + */ + protected function buildIndexFromFilesystem($path) + { + $flags = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS; + + $iterator = new FilesystemIterator($path, $flags); + $list = []; + /** @var SplFileInfo $info */ + foreach ($iterator as $filename => $info) { + if (!$info->isDir() || strpos($info->getFilename(), '.') === 0) { + continue; + } + + $key = $this->getKeyFromPath($filename); + $meta = $this->getObjectMeta($key); + if ($meta['storage_timestamp']) { + $list[$key] = $meta; + } + } + + return $list; + } + + /** + * @param string $path + * @return array + */ + protected function buildPrefixedIndexFromFilesystem($path) + { + $flags = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS; + + $iterator = new FilesystemIterator($path, $flags); + $list = [[]]; + /** @var SplFileInfo $info */ + foreach ($iterator as $filename => $info) { + if (!$info->isDir() || strpos($info->getFilename(), '.') === 0) { + continue; + } + + $list[] = $this->buildIndexFromFilesystem($filename); + } + + return array_merge(...$list); + } + + /** + * @return string + */ + protected function getNewKey(): string + { + // Make sure that the file doesn't exist. + do { + $key = $this->generateKey(); + } while (file_exists($this->getPathFromKey($key))); + + return $key; + } + + /** + * @param array $options + * @return void + */ + protected function initOptions(array $options): void + { + $extension = $this->dataFormatter->getDefaultFileExtension(); + + /** @var string $pattern */ + $pattern = !empty($options['pattern']) ? $options['pattern'] : $this->dataPattern; + + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + $folder = $options['folder']; + if ($locator->isStream($folder)) { + $folder = $locator->getResource($folder, false); + } + + $this->dataFolder = $folder; + $this->dataFile = $options['file'] ?? 'item'; + $this->dataExt = $extension; + if (mb_strpos($pattern, '{FILE}') === false && mb_strpos($pattern, '{EXT}') === false) { + if (isset($options['file'])) { + $pattern .= '/{FILE}{EXT}'; + } else { + $filesystem = Filesystem::getInstance(true); + $this->dataFile = Utils::basename($pattern, $extension); + $pattern = $filesystem->dirname($pattern) . '/{FILE}{EXT}'; + } + } + $this->prefixed = (bool)($options['prefixed'] ?? strpos($pattern, '/{KEY:2}/')); + $this->indexed = (bool)($options['indexed'] ?? false); + $this->keyField = $options['key'] ?? 'storage_key'; + $this->keyLen = (int)($options['key_len'] ?? 32); + $this->caseSensitive = (bool)($options['case_sensitive'] ?? true); + + $pattern = Utils::simpleTemplate($pattern, $this->variables); + if (!$pattern) { + throw new RuntimeException('Bad storage folder pattern'); + } + + $this->dataPattern = $pattern; + } +} diff --git a/system/src/Grav/Framework/Flex/Storage/SimpleStorage.php b/system/src/Grav/Framework/Flex/Storage/SimpleStorage.php new file mode 100644 index 0000000..99c7102 --- /dev/null +++ b/system/src/Grav/Framework/Flex/Storage/SimpleStorage.php @@ -0,0 +1,507 @@ +detectDataFormatter($options['folder']); + $this->initDataFormatter($formatter); + + $filesystem = Filesystem::getInstance(true); + + $extension = $this->dataFormatter->getDefaultFileExtension(); + $pattern = Utils::basename($options['folder']); + + $this->dataPattern = Utils::basename($pattern, $extension) . $extension; + $this->dataFolder = $filesystem->dirname($options['folder']); + $this->keyField = $options['key'] ?? 'storage_key'; + $this->keyLen = (int)($options['key_len'] ?? 32); + $this->prefix = $options['prefix'] ?? null; + + // Make sure that the data folder exists. + if (!file_exists($this->dataFolder)) { + try { + Folder::create($this->dataFolder); + } catch (RuntimeException $e) { + throw new RuntimeException(sprintf('Flex: %s', $e->getMessage())); + } + } + } + + /** + * @return void + */ + public function clearCache(): void + { + $this->data = null; + $this->modified = 0; + } + + /** + * @param string[] $keys + * @param bool $reload + * @return array + */ + public function getMetaData(array $keys, bool $reload = false): array + { + if (null === $this->data || $reload) { + $this->buildIndex(); + } + + $list = []; + foreach ($keys as $key) { + $list[$key] = $this->getObjectMeta((string)$key); + } + + return $list; + } + + /** + * {@inheritdoc} + * @see FlexStorageInterface::getExistingKeys() + */ + public function getExistingKeys(): array + { + return $this->buildIndex(); + } + + /** + * {@inheritdoc} + * @see FlexStorageInterface::hasKey() + */ + public function hasKey(string $key): bool + { + if (null === $this->data) { + $this->buildIndex(); + } + + return $key && strpos($key, '@@') === false && isset($this->data[$key]); + } + + /** + * {@inheritdoc} + * @see FlexStorageInterface::createRows() + */ + public function createRows(array $rows): array + { + if (null === $this->data) { + $this->buildIndex(); + } + + $list = []; + foreach ($rows as $key => $row) { + $list[$key] = $this->saveRow('@@', $rows); + } + + if ($list) { + $this->save(); + } + + return $list; + } + + /** + * {@inheritdoc} + * @see FlexStorageInterface::readRows() + */ + public function readRows(array $rows, array &$fetched = null): array + { + if (null === $this->data) { + $this->buildIndex(); + } + + $list = []; + foreach ($rows as $key => $row) { + if (null === $row || is_scalar($row)) { + // Only load rows which haven't been loaded before. + $key = (string)$key; + $list[$key] = $this->hasKey($key) ? $this->loadRow($key) : null; + if (null !== $fetched) { + $fetched[$key] = $list[$key]; + } + } else { + // Keep the row if it has been loaded. + $list[$key] = $row; + } + } + + return $list; + } + + /** + * {@inheritdoc} + * @see FlexStorageInterface::updateRows() + */ + public function updateRows(array $rows): array + { + if (null === $this->data) { + $this->buildIndex(); + } + + $save = false; + $list = []; + foreach ($rows as $key => $row) { + $key = (string)$key; + if ($this->hasKey($key)) { + $list[$key] = $this->saveRow($key, $row); + $save = true; + } else { + $list[$key] = null; + } + } + + if ($save) { + $this->save(); + } + + return $list; + } + + /** + * {@inheritdoc} + * @see FlexStorageInterface::deleteRows() + */ + public function deleteRows(array $rows): array + { + if (null === $this->data) { + $this->buildIndex(); + } + + $list = []; + foreach ($rows as $key => $row) { + $key = (string)$key; + if ($this->hasKey($key)) { + unset($this->data[$key]); + $list[$key] = $row; + } + } + + if ($list) { + $this->save(); + } + + return $list; + } + + /** + * {@inheritdoc} + * @see FlexStorageInterface::replaceRows() + */ + public function replaceRows(array $rows): array + { + if (null === $this->data) { + $this->buildIndex(); + } + + $list = []; + foreach ($rows as $key => $row) { + $list[$key] = $this->saveRow((string)$key, $row); + } + + if ($list) { + $this->save(); + } + + return $list; + } + + /** + * @param string $src + * @param string $dst + * @return bool + */ + public function copyRow(string $src, string $dst): bool + { + if ($this->hasKey($dst)) { + throw new RuntimeException("Cannot copy object: key '{$dst}' is already taken"); + } + + if (!$this->hasKey($src)) { + return false; + } + + $this->data[$dst] = $this->data[$src]; + + return true; + } + + /** + * {@inheritdoc} + * @see FlexStorageInterface::renameRow() + */ + public function renameRow(string $src, string $dst): bool + { + if (null === $this->data) { + $this->buildIndex(); + } + + if ($this->hasKey($dst)) { + throw new RuntimeException("Cannot rename object: key '{$dst}' is already taken"); + } + + if (!$this->hasKey($src)) { + return false; + } + + // Change single key in the array without changing the order or value. + $keys = array_keys($this->data); + $keys[array_search($src, $keys, true)] = $dst; + + $data = array_combine($keys, $this->data); + if (false === $data) { + throw new LogicException('Bad data'); + } + + $this->data = $data; + + return true; + } + + /** + * {@inheritdoc} + * @see FlexStorageInterface::getStoragePath() + */ + public function getStoragePath(string $key = null): ?string + { + return $this->dataFolder . '/' . $this->dataPattern; + } + + /** + * {@inheritdoc} + * @see FlexStorageInterface::getMediaPath() + */ + public function getMediaPath(string $key = null): ?string + { + return null; + } + + /** + * Prepares the row for saving and returns the storage key for the record. + * + * @param array $row + */ + protected function prepareRow(array &$row): void + { + unset($row[$this->keyField]); + } + + /** + * @param string $key + * @return array + */ + protected function loadRow(string $key): ?array + { + $data = $this->data[$key] ?? []; + if ($this->keyField !== 'storage_key') { + $data[$this->keyField] = $key; + } + $data['__META'] = $this->getObjectMeta($key); + + return $data; + } + + /** + * @param string $key + * @param array $row + * @return array + */ + protected function saveRow(string $key, array $row): array + { + try { + if (isset($row[$this->keyField])) { + $key = $row[$this->keyField]; + } + if (strpos($key, '@@') !== false) { + $key = $this->getNewKey(); + } + + // Check if the row already exists and if the key has been changed. + $oldKey = $row['__META']['storage_key'] ?? null; + if (is_string($oldKey) && $oldKey !== $key) { + $isCopy = $row['__META']['copy'] ?? false; + if ($isCopy) { + $this->copyRow($oldKey, $key); + } else { + $this->renameRow($oldKey, $key); + } + } + + $this->prepareRow($row); + unset($row['__META'], $row['__ERROR']); + + $this->data[$key] = $row; + } catch (RuntimeException $e) { + throw new RuntimeException(sprintf('Flex saveRow(%s): %s', $key, $e->getMessage())); + } + + $row['__META'] = $this->getObjectMeta($key, true); + + return $row; + } + + /** + * @param string $key + * @param bool $variations + * @return array + */ + public function parseKey(string $key, bool $variations = true): array + { + return [ + 'key' => $key, + ]; + } + + protected function save(): void + { + if (null === $this->data) { + $this->buildIndex(); + } + + try { + $path = $this->getStoragePath(); + if (!$path) { + throw new RuntimeException('Storage path is not defined'); + } + $file = $this->getFile($path); + if ($this->prefix) { + $data = new Data((array)$file->content()); + $content = $data->set($this->prefix, $this->data)->toArray(); + } else { + $content = $this->data; + } + $file->save($content); + $this->modified = (int)$file->modified(); // cast false to 0 + } catch (RuntimeException $e) { + throw new RuntimeException(sprintf('Flex save(): %s', $e->getMessage())); + } finally { + if (isset($file)) { + $file->free(); + unset($file); + } + } + } + + /** + * Get key from the filesystem path. + * + * @param string $path + * @return string + */ + protected function getKeyFromPath(string $path): string + { + return Utils::basename($path); + } + + /** + * Returns list of all stored keys in [key => timestamp] pairs. + * + * @return array + */ + protected function buildIndex(): array + { + $path = $this->getStoragePath(); + if (!$path) { + $this->data = []; + + return []; + } + + $file = $this->getFile($path); + $this->modified = (int)$file->modified(); // cast false to 0 + + $content = (array) $file->content(); + if ($this->prefix) { + $data = new Data($content); + $content = $data->get($this->prefix, []); + } + + $file->free(); + unset($file); + + $this->data = $content; + + $list = []; + foreach ($this->data as $key => $info) { + $list[$key] = $this->getObjectMeta((string)$key); + } + + return $list; + } + + /** + * @param string $key + * @param bool $reload + * @return array + */ + protected function getObjectMeta(string $key, bool $reload = false): array + { + $modified = isset($this->data[$key]) ? $this->modified : 0; + + return [ + 'storage_key' => $key, + 'key' => $key, + 'storage_timestamp' => $modified + ]; + } + + /** + * @return string + */ + protected function getNewKey(): string + { + if (null === $this->data) { + $this->buildIndex(); + } + + // Make sure that the key doesn't exist. + do { + $key = $this->generateKey(); + } while (isset($this->data[$key])); + + return $key; + } +} diff --git a/system/src/Grav/Framework/Flex/Traits/FlexAuthorizeTrait.php b/system/src/Grav/Framework/Flex/Traits/FlexAuthorizeTrait.php new file mode 100644 index 0000000..6f340a5 --- /dev/null +++ b/system/src/Grav/Framework/Flex/Traits/FlexAuthorizeTrait.php @@ -0,0 +1,126 @@ +getAuthorizeAction($action); + $scope = $scope ?? $this->getAuthorizeScope(); + + $isMe = null === $user; + if ($isMe) { + $user = $this->getActiveUser(); + } + + if (null === $user) { + return false; + } + + // Finally authorize against given action. + return $this->isAuthorizedOverride($user, $action, $scope, $isMe); + } + + /** + * Please override this method + * + * @param UserInterface $user + * @param string $action + * @param string $scope + * @param bool $isMe + * @return bool|null + */ + protected function isAuthorizedOverride(UserInterface $user, string $action, string $scope, bool $isMe): ?bool + { + return $this->isAuthorizedAction($user, $action, $scope, $isMe); + } + + /** + * Check if user is authorized for the action. + * + * @param UserInterface $user + * @param string $action + * @param string $scope + * @param bool $isMe + * @return bool|null + */ + protected function isAuthorizedAction(UserInterface $user, string $action, string $scope, bool $isMe): ?bool + { + // Check if the action has been denied in the flex type configuration. + $directory = $this instanceof FlexDirectory ? $this : $this->getFlexDirectory(); + $config = $directory->getConfig(); + $allowed = $config->get("{$scope}.actions.{$action}") ?? $config->get("actions.{$action}") ?? true; + if (false === $allowed) { + return false; + } + + // TODO: Not needed anymore with flex users, remove in 2.0. + $auth = $user instanceof FlexObjectInterface ? null : $user->authorize('admin.super'); + if (true === $auth) { + return true; + } + + // Finally authorize the action. + return $user->authorize($this->getAuthorizeRule($scope, $action), !$isMe ? 'test' : null); + } + + /** + * @param UserInterface $user + * @return bool|null + * @deprecated 1.7 Not needed for Flex Users. + */ + protected function isAuthorizedSuperAdmin(UserInterface $user): ?bool + { + // Action authorization includes super user authorization if using Flex Users. + if ($user instanceof FlexObjectInterface) { + return null; + } + + return $user->authorize('admin.super'); + } + + /** + * @param string $scope + * @param string $action + * @return string + */ + protected function getAuthorizeRule(string $scope, string $action): string + { + if ($this instanceof FlexDirectory) { + return $this->getAuthorizeRule($scope, $action); + } + + return $this->getFlexDirectory()->getAuthorizeRule($scope, $action); + } +} diff --git a/system/src/Grav/Framework/Flex/Traits/FlexMediaTrait.php b/system/src/Grav/Framework/Flex/Traits/FlexMediaTrait.php new file mode 100644 index 0000000..35e3900 --- /dev/null +++ b/system/src/Grav/Framework/Flex/Traits/FlexMediaTrait.php @@ -0,0 +1,576 @@ +exists() ? $this->getFlexDirectory()->getStorageFolder($this->getStorageKey()) : null; + } + + /** + * @return string|null + */ + public function getMediaFolder() + { + return $this->exists() ? $this->getFlexDirectory()->getMediaFolder($this->getStorageKey()) : null; + } + + /** + * @return MediaCollectionInterface + */ + public function getMedia() + { + $media = $this->media; + if (null === $media) { + $media = $this->getExistingMedia(); + + // Include uploaded media to the object media. + $this->addUpdatedMedia($media); + } + + return $media; + } + + /** + * @param string $field + * @return MediaCollectionInterface|null + */ + public function getMediaField(string $field): ?MediaCollectionInterface + { + // Field specific media. + $settings = $this->getFieldSettings($field); + if (!empty($settings['media_field'])) { + $var = 'destination'; + } elseif (!empty($settings['media_picker_field'])) { + $var = 'folder'; + } + + if (empty($var)) { + // Not a media field. + $media = null; + } elseif ($settings['self']) { + // Uses main media. + $media = $this->getMedia(); + } else { + // Uses custom media. + $media = new Media($settings[$var]); + $this->addUpdatedMedia($media); + } + + return $media; + } + + /** + * @param string $field + * @return array|null + */ + public function getFieldSettings(string $field): ?array + { + if ($field === '') { + return null; + } + + // Load settings for the field. + $schema = $this->getBlueprint()->schema(); + $settings = (array)$schema->getProperty($field); + if (!is_array($settings)) { + return null; + } + + $type = $settings['type'] ?? ''; + + // Media field. + if (!empty($settings['media_field']) || array_key_exists('destination', $settings) || in_array($type, ['avatar', 'file', 'pagemedia'], true)) { + $settings['media_field'] = true; + $var = 'destination'; + } + + // Media picker field. + if (!empty($settings['media_picker_field']) || in_array($type, ['filepicker', 'pagemediaselect'], true)) { + $settings['media_picker_field'] = true; + $var = 'folder'; + } + + // Set media folder for media fields. + if (isset($var)) { + $folder = $settings[$var] ?? ''; + if (in_array(rtrim($folder, '/'), ['', '@self', 'self@', '@self@'], true)) { + $settings[$var] = $this->getMediaFolder(); + $settings['self'] = true; + } else { + $settings[$var] = Utils::getPathFromToken($folder, $this); + $settings['self'] = false; + } + } + + return $settings; + } + + /** + * @param string $field + * @return array + * @internal + */ + protected function getMediaFieldSettings(string $field): array + { + $settings = $this->getFieldSettings($field) ?? []; + + return $settings + ['accept' => '*', 'limit' => 1000, 'self' => true]; + } + + /** + * @return array + */ + protected function getMediaFields(): array + { + // Load settings for the field. + $schema = $this->getBlueprint()->schema(); + + $list = []; + foreach ($schema->getState()['items'] as $field => $settings) { + if (isset($settings['type']) && (in_array($settings['type'], ['avatar', 'file', 'pagemedia']) || !empty($settings['destination']))) { + $list[] = $field; + } + } + + return $list; + } + + /** + * @param array|mixed $value + * @param array $settings + * @return array|mixed + */ + protected function parseFileProperty($value, array $settings = []) + { + if (!is_array($value)) { + return $value; + } + + $media = $this->getMedia(); + $originalMedia = is_callable([$this, 'getOriginalMedia']) ? $this->getOriginalMedia() : null; + + $list = []; + foreach ($value as $filename => $info) { + if (!is_array($info)) { + $list[$filename] = $info; + continue; + } + + if (is_int($filename)) { + $filename = $info['path'] ?? $info['name']; + } + + /** @var Medium|null $imageFile */ + $imageFile = $media[$filename]; + + /** @var Medium|null $originalFile */ + $originalFile = $originalMedia ? $originalMedia[$filename] : null; + + $url = $imageFile ? $imageFile->url() : null; + $originalUrl = $originalFile ? $originalFile->url() : null; + $list[$filename] = [ + 'name' => $info['name'] ?? null, + 'type' => $info['type'] ?? null, + 'size' => $info['size'] ?? null, + 'path' => $filename, + 'thumb_url' => $url, + 'image_url' => $originalUrl ?? $url + ]; + if ($originalFile) { + $list[$filename]['cropData'] = (object)($originalFile->metadata()['upload']['crop'] ?? []); + } + } + + return $list; + } + + /** + * @param UploadedFileInterface $uploadedFile + * @param string|null $filename + * @param string|null $field + * @return void + * @internal + */ + public function checkUploadedMediaFile(UploadedFileInterface $uploadedFile, string $filename = null, string $field = null) + { + $media = $this->getMedia(); + if (!$media instanceof MediaUploadInterface) { + throw new RuntimeException("Media for {$this->getFlexDirectory()->getFlexType()} doesn't support file uploads."); + } + + $media->checkUploadedFile($uploadedFile, $filename, $this->getMediaFieldSettings($field ?? '')); + } + + /** + * @param UploadedFileInterface $uploadedFile + * @param string|null $filename + * @param string|null $field + * @return void + * @internal + */ + public function uploadMediaFile(UploadedFileInterface $uploadedFile, string $filename = null, string $field = null): void + { + $settings = $this->getMediaFieldSettings($field ?? ''); + + $media = $field ? $this->getMediaField($field) : $this->getMedia(); + if (!$media instanceof MediaUploadInterface) { + throw new RuntimeException("Media for {$this->getFlexDirectory()->getFlexType()} doesn't support file uploads."); + } + + $filename = $media->checkUploadedFile($uploadedFile, $filename, $settings); + $media->copyUploadedFile($uploadedFile, $filename, $settings); + $this->clearMediaCache(); + } + + /** + * @param string $filename + * @return void + * @internal + */ + public function deleteMediaFile(string $filename): void + { + $media = $this->getMedia(); + if (!$media instanceof MediaUploadInterface) { + throw new RuntimeException("Media for {$this->getFlexDirectory()->getFlexType()} doesn't support file uploads."); + } + + $media->deleteFile($filename); + $this->clearMediaCache(); + } + + /** + * @return array + */ + #[\ReturnTypeWillChange] + public function __debugInfo() + { + return parent::__debugInfo() + [ + 'uploads:private' => $this->getUpdatedMedia() + ]; + } + + /** + * @param string|null $field + * @param string $filename + * @param MediaObjectInterface|null $image + * @return MediaObject|UploadedMediaObject + */ + protected function buildMediaObject(?string $field, string $filename, MediaObjectInterface $image = null) + { + if (!$image) { + $media = $field ? $this->getMediaField($field) : null; + if ($media) { + $image = $media[$filename]; + } + } + + return new MediaObject($field, $filename, $image, $this); + } + + /** + * @param string|null $field + * @return array + */ + protected function buildMediaList(?string $field): array + { + $names = $field ? (array)$this->getNestedProperty($field) : []; + $media = $field ? $this->getMediaField($field) : null; + if (null === $media) { + $media = $this->getMedia(); + } + + $list = []; + foreach ($names as $key => $val) { + $name = is_string($val) ? $val : $key; + $medium = $media[$name]; + if ($medium) { + if ($medium->uploaded_file) { + $upload = $medium->uploaded_file; + $id = $upload instanceof FormFlashFile ? $upload->getId() : "{$field}-{$name}"; + + $list[] = new UploadedMediaObject($id, $field, $name, $upload); + } else { + $list[] = $this->buildMediaObject($field, $name, $medium); + } + } + } + + return $list; + } + + /** + * @param array $files + * @return void + */ + protected function setUpdatedMedia(array $files): void + { + $media = $this->getMedia(); + if (!$media instanceof MediaUploadInterface) { + return; + } + + $filesystem = Filesystem::getInstance(false); + + $list = []; + foreach ($files as $field => $group) { + $field = (string)$field; + // Ignore files without a field and resized images. + if ($field === '' || strpos($field, '/')) { + continue; + } + + // Load settings for the field. + $settings = $this->getMediaFieldSettings($field); + foreach ($group as $filename => $file) { + if ($file) { + // File upload. + $filename = $file->getClientFilename(); + + /** @var FormFlashFile $file */ + $data = $file->jsonSerialize(); + unset($data['tmp_name'], $data['path']); + } else { + // File delete. + $data = null; + } + + if ($file) { + // Check file upload against media limits (except for max size). + $filename = $media->checkUploadedFile($file, $filename, ['filesize' => 0] + $settings); + } + + $self = $settings['self']; + if ($this->_loadMedia && $self) { + $filepath = $filename; + } else { + $filepath = "{$settings['destination']}/{$filename}"; + } + + // Calculate path without the retina scaling factor. + $realpath = $filesystem->pathname($filepath) . str_replace(['@3x', '@2x'], '', Utils::basename($filepath)); + + $list[$filename] = [$file, $settings]; + + $path = str_replace('.', "\n", $field); + if (null !== $data) { + $data['name'] = $filename; + $data['path'] = $filepath; + + $this->setNestedProperty("{$path}\n{$realpath}", $data, "\n"); + } else { + $this->unsetNestedProperty("{$path}\n{$realpath}", "\n"); + } + } + } + + $this->clearMediaCache(); + + $this->_uploads = $list; + } + + /** + * @param MediaCollectionInterface $media + */ + protected function addUpdatedMedia(MediaCollectionInterface $media): void + { + $updated = false; + foreach ($this->getUpdatedMedia() as $filename => $upload) { + if (is_array($upload)) { + /** @var array{UploadedFileInterface,array} $upload */ + $settings = $upload[1]; + if (isset($settings['destination']) && $settings['destination'] === $media->getPath()) { + $upload = $upload[0]; + } else { + $upload = false; + } + } + if (false !== $upload) { + $medium = $upload ? MediumFactory::fromUploadedFile($upload) : null; + $updated = true; + if ($medium) { + $medium->uploaded = true; + $medium->uploaded_file = $upload; + $media->add($filename, $medium); + } elseif (is_callable([$media, 'hide'])) { + $media->hide($filename); + } + } + } + + if ($updated) { + $media->setTimestamps(); + } + } + + /** + * @return array + */ + protected function getUpdatedMedia(): array + { + return $this->_uploads; + } + + /** + * @return void + */ + protected function saveUpdatedMedia(): void + { + $media = $this->getMedia(); + if (!$media instanceof MediaUploadInterface) { + return; + } + + // Upload/delete altered files. + /** + * @var string $filename + * @var UploadedFileInterface|array|null $file + */ + foreach ($this->getUpdatedMedia() as $filename => $file) { + if (is_array($file)) { + [$file, $settings] = $file; + } else { + $settings = null; + } + if ($file instanceof UploadedFileInterface) { + $media->copyUploadedFile($file, $filename, $settings); + } else { + $media->deleteFile($filename, $settings); + } + } + + $this->setUpdatedMedia([]); + $this->clearMediaCache(); + } + + /** + * @return void + */ + protected function freeMedia(): void + { + $this->unsetObjectProperty('media'); + } + + /** + * @param string $uri + * @return Medium|null + */ + protected function createMedium($uri) + { + $grav = Grav::instance(); + + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + + $file = $uri && $locator->isStream($uri) ? $locator->findResource($uri) : $uri; + + return is_string($file) && file_exists($file) ? MediumFactory::fromFile($file) : null; + } + + /** + * @return CacheInterface + */ + protected function getMediaCache() + { + return $this->getCache('object'); + } + + /** + * @return MediaCollectionInterface + */ + protected function offsetLoad_media() + { + return $this->getMedia(); + } + + /** + * @return null + */ + protected function offsetSerialize_media() + { + return null; + } + + /** + * @return FlexDirectory + */ + abstract public function getFlexDirectory(): FlexDirectory; + + /** + * @return string + */ + abstract public function getStorageKey(): string; + + /** + * @param string $filename + * @return void + * @deprecated 1.7 Use Media class that implements MediaUploadInterface instead. + */ + public function checkMediaFilename(string $filename) + { + user_error(__METHOD__ . '() is deprecated since Grav 1.7, use Media class that implements MediaUploadInterface instead', E_USER_DEPRECATED); + + // Check the file extension. + $extension = strtolower(Utils::pathinfo($filename, PATHINFO_EXTENSION)); + + $grav = Grav::instance(); + + /** @var Config $config */ + $config = $grav['config']; + + // If not a supported type, return + if (!$extension || !$config->get("media.types.{$extension}")) { + $language = $grav['language']; + throw new RuntimeException($language->translate('PLUGIN_ADMIN.UNSUPPORTED_FILE_TYPE') . ': ' . $extension, 400); + } + } +} diff --git a/system/src/Grav/Framework/Flex/Traits/FlexRelatedDirectoryTrait.php b/system/src/Grav/Framework/Flex/Traits/FlexRelatedDirectoryTrait.php new file mode 100644 index 0000000..e162f8c --- /dev/null +++ b/system/src/Grav/Framework/Flex/Traits/FlexRelatedDirectoryTrait.php @@ -0,0 +1,59 @@ + + */ + protected function getCollectionByProperty($type, $property) + { + $directory = $this->getRelatedDirectory($type); + $collection = $directory->getCollection(); + $list = $this->getNestedProperty($property) ?: []; + + /** @var FlexCollectionInterface $collection */ + $collection = $collection->filter(static function ($object) use ($list) { + return in_array($object->getKey(), $list, true); + }); + + return $collection; + } + + /** + * @param string $type + * @return FlexDirectory + * @throws RuntimeException + */ + protected function getRelatedDirectory($type): FlexDirectory + { + $directory = $this->getFlexContainer()->getDirectory($type); + if (!$directory) { + throw new RuntimeException(ucfirst($type). ' directory does not exist!'); + } + + return $directory; + } +} diff --git a/system/src/Grav/Framework/Flex/Traits/FlexRelationshipsTrait.php b/system/src/Grav/Framework/Flex/Traits/FlexRelationshipsTrait.php new file mode 100644 index 0000000..2a73eba --- /dev/null +++ b/system/src/Grav/Framework/Flex/Traits/FlexRelationshipsTrait.php @@ -0,0 +1,61 @@ +_relationships)) { + $blueprint = $this->getBlueprint(); + $options = $blueprint->get('config/relationships', []); + $parent = FlexIdentifier::createFromObject($this); + + $this->_relationships = new Relationships($parent, $options); + } + + return $this->_relationships; + } + + /** + * @param string $name + * @return RelationshipInterface|null + */ + public function getRelationship(string $name): ?RelationshipInterface + { + return $this->getRelationships()[$name]; + } + + protected function resetRelationships(): void + { + $this->_relationships = null; + } + + /** + * @param iterable $collection + * @return array + */ + protected function buildFlexIdentifierList(iterable $collection): array + { + $list = []; + foreach ($collection as $object) { + $list[] = FlexIdentifier::createFromObject($object); + } + + return $list; + } +} diff --git a/system/src/Grav/Framework/Form/FormFlash.php b/system/src/Grav/Framework/Form/FormFlash.php new file mode 100644 index 0000000..c70a5e0 --- /dev/null +++ b/system/src/Grav/Framework/Form/FormFlash.php @@ -0,0 +1,586 @@ + $args[0], + 'unique_id' => $args[1] ?? null, + 'form_name' => $args[2] ?? null, + ]; + $config = array_filter($config, static function ($val) { + return $val !== null; + }); + } + + $this->id = $config['id'] ?? ''; + $this->sessionId = $config['session_id'] ?? ''; + $this->uniqueId = $config['unique_id'] ?? ''; + + $this->setUser($config['user'] ?? null); + + $folder = $config['folder'] ?? ($this->sessionId ? 'tmp://forms/' . $this->sessionId : ''); + + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + + $this->folder = $folder && $locator->isStream($folder) ? $locator->findResource($folder, true, true) : $folder; + + $this->init($this->loadStoredForm(), $config); + } + + /** + * @param array|null $data + * @param array $config + */ + protected function init(?array $data, array $config): void + { + if (null === $data) { + $this->exists = false; + $this->formName = $config['form_name'] ?? ''; + $this->url = ''; + $this->createdTimestamp = $this->updatedTimestamp = time(); + $this->files = []; + } else { + $this->exists = true; + $this->formName = $data['form'] ?? $config['form_name'] ?? ''; + $this->url = $data['url'] ?? ''; + $this->user = $data['user'] ?? null; + $this->updatedTimestamp = $data['timestamps']['updated'] ?? time(); + $this->createdTimestamp = $data['timestamps']['created'] ?? $this->updatedTimestamp; + $this->data = $data['data'] ?? null; + $this->files = $data['files'] ?? []; + } + } + + /** + * Load raw flex flash data from the filesystem. + * + * @return array|null + */ + protected function loadStoredForm(): ?array + { + $file = $this->getTmpIndex(); + $exists = $file && $file->exists(); + + $data = null; + if ($exists) { + try { + $data = (array)$file->content(); + } catch (Exception $e) { + } + } + + return $data; + } + + /** + * @inheritDoc + */ + public function getId(): string + { + return $this->id && $this->uniqueId ? $this->id . '/' . $this->uniqueId : ''; + } + + /** + * @inheritDoc + */ + public function getSessionId(): string + { + return $this->sessionId; + } + + /** + * @inheritDoc + */ + public function getUniqueId(): string + { + return $this->uniqueId; + } + + /** + * @return string + * @deprecated 1.6.11 Use '->getUniqueId()' method instead. + */ + public function getUniqieId(): string + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6.11, use ->getUniqueId() method instead', E_USER_DEPRECATED); + + return $this->getUniqueId(); + } + + /** + * @inheritDoc + */ + public function getFormName(): string + { + return $this->formName; + } + + + /** + * @inheritDoc + */ + public function getUrl(): string + { + return $this->url; + } + + /** + * @inheritDoc + */ + public function getUsername(): string + { + return $this->user['username'] ?? ''; + } + + /** + * @inheritDoc + */ + public function getUserEmail(): string + { + return $this->user['email'] ?? ''; + } + + /** + * @inheritDoc + */ + public function getCreatedTimestamp(): int + { + return $this->createdTimestamp; + } + + /** + * @inheritDoc + */ + public function getUpdatedTimestamp(): int + { + return $this->updatedTimestamp; + } + + + /** + * @inheritDoc + */ + public function getData(): ?array + { + return $this->data; + } + + /** + * @inheritDoc + */ + public function setData(?array $data): void + { + $this->data = $data; + } + + /** + * @inheritDoc + */ + public function exists(): bool + { + return $this->exists; + } + + /** + * @inheritDoc + */ + public function save(bool $force = false) + { + if (!($this->folder && $this->uniqueId)) { + return $this; + } + + if ($force || $this->data || $this->files) { + // Only save if there is data or files to be saved. + $file = $this->getTmpIndex(); + if ($file) { + $file->save($this->jsonSerialize()); + $this->exists = true; + } + } elseif ($this->exists) { + // Delete empty form flash if it exists (it carries no information). + return $this->delete(); + } + + return $this; + } + + /** + * @inheritDoc + */ + public function delete() + { + if ($this->folder && $this->uniqueId) { + $this->removeTmpDir(); + $this->files = []; + $this->exists = false; + } + + return $this; + } + + /** + * @inheritDoc + */ + public function getFilesByField(string $field): array + { + if (!isset($this->uploadObjects[$field])) { + $objects = []; + foreach ($this->files[$field] ?? [] as $name => $upload) { + $objects[$name] = $upload ? new FormFlashFile($field, $upload, $this) : null; + } + $this->uploadedFiles[$field] = $objects; + } + + return $this->uploadedFiles[$field]; + } + + /** + * @inheritDoc + */ + public function getFilesByFields($includeOriginal = false): array + { + $list = []; + foreach ($this->files as $field => $values) { + if (!$includeOriginal && strpos($field, '/')) { + continue; + } + $list[$field] = $this->getFilesByField($field); + } + + return $list; + } + + /** + * @inheritDoc + */ + public function addUploadedFile(UploadedFileInterface $upload, string $field = null, array $crop = null): string + { + $tmp_dir = $this->getTmpDir(); + $tmp_name = Utils::generateRandomString(12); + $name = $upload->getClientFilename(); + if (!$name) { + throw new RuntimeException('Uploaded file has no filename'); + } + + // Prepare upload data for later save + $data = [ + 'name' => $name, + 'type' => $upload->getClientMediaType(), + 'size' => $upload->getSize(), + 'tmp_name' => $tmp_name + ]; + + Folder::create($tmp_dir); + $upload->moveTo("{$tmp_dir}/{$tmp_name}"); + + $this->addFileInternal($field, $name, $data, $crop); + + return $name; + } + + /** + * @inheritDoc + */ + public function addFile(string $filename, string $field, array $crop = null): bool + { + if (!file_exists($filename)) { + throw new RuntimeException("File not found: {$filename}"); + } + + // Prepare upload data for later save + $data = [ + 'name' => Utils::basename($filename), + 'type' => Utils::getMimeByLocalFile($filename), + 'size' => filesize($filename), + ]; + + $this->addFileInternal($field, $data['name'], $data, $crop); + + return true; + } + + /** + * @inheritDoc + */ + public function removeFile(string $name, string $field = null): bool + { + if (!$name) { + return false; + } + + $field = $field ?: 'undefined'; + + $upload = $this->files[$field][$name] ?? null; + if (null !== $upload) { + $this->removeTmpFile($upload['tmp_name'] ?? ''); + } + $upload = $this->files[$field . '/original'][$name] ?? null; + if (null !== $upload) { + $this->removeTmpFile($upload['tmp_name'] ?? ''); + } + + // Mark file as deleted. + $this->files[$field][$name] = null; + $this->files[$field . '/original'][$name] = null; + + unset( + $this->uploadedFiles[$field][$name], + $this->uploadedFiles[$field . '/original'][$name] + ); + + return true; + } + + /** + * @inheritDoc + */ + public function clearFiles() + { + foreach ($this->files as $files) { + foreach ($files as $upload) { + $this->removeTmpFile($upload['tmp_name'] ?? ''); + } + } + + $this->files = []; + } + + /** + * @inheritDoc + */ + public function jsonSerialize(): array + { + return [ + 'form' => $this->formName, + 'id' => $this->getId(), + 'unique_id' => $this->uniqueId, + 'url' => $this->url, + 'user' => $this->user, + 'timestamps' => [ + 'created' => $this->createdTimestamp, + 'updated' => time(), + ], + 'data' => $this->data, + 'files' => $this->files + ]; + } + + /** + * @param string $url + * @return $this + */ + public function setUrl(string $url): self + { + $this->url = $url; + + return $this; + } + + /** + * @param UserInterface|null $user + * @return $this + */ + public function setUser(UserInterface $user = null) + { + if ($user && $user->username) { + $this->user = [ + 'username' => $user->username, + 'email' => $user->email ?? '' + ]; + } else { + $this->user = null; + } + + return $this; + } + + /** + * @param string|null $username + * @return $this + */ + public function setUserName(string $username = null): self + { + $this->user['username'] = $username; + + return $this; + } + + /** + * @param string|null $email + * @return $this + */ + public function setUserEmail(string $email = null): self + { + $this->user['email'] = $email; + + return $this; + } + + /** + * @return string + */ + public function getTmpDir(): string + { + return $this->folder && $this->uniqueId ? "{$this->folder}/{$this->uniqueId}" : ''; + } + + /** + * @return ?YamlFile + */ + protected function getTmpIndex(): ?YamlFile + { + $tmpDir = $this->getTmpDir(); + + // Do not use CompiledYamlFile as the file can change multiple times per second. + return $tmpDir ? YamlFile::instance($tmpDir . '/index.yaml') : null; + } + + /** + * @param string $name + */ + protected function removeTmpFile(string $name): void + { + $tmpDir = $this->getTmpDir(); + $filename = $tmpDir ? $tmpDir . '/' . $name : ''; + if ($name && $filename && is_file($filename)) { + unlink($filename); + } + } + + /** + * @return void + */ + protected function removeTmpDir(): void + { + // Make sure that index file cache gets always cleared. + $file = $this->getTmpIndex(); + if ($file) { + $file->free(); + } + + $tmpDir = $this->getTmpDir(); + if ($tmpDir && file_exists($tmpDir)) { + Folder::delete($tmpDir); + } + } + + /** + * @param string|null $field + * @param string $name + * @param array $data + * @param array|null $crop + * @return void + */ + protected function addFileInternal(?string $field, string $name, array $data, array $crop = null): void + { + if (!($this->folder && $this->uniqueId)) { + throw new RuntimeException('Cannot upload files: form flash folder not defined'); + } + + $field = $field ?: 'undefined'; + if (!isset($this->files[$field])) { + $this->files[$field] = []; + } + + $oldUpload = $this->files[$field][$name] ?? null; + + if ($crop) { + // Deal with crop upload + if ($oldUpload) { + $originalUpload = $this->files[$field . '/original'][$name] ?? null; + if ($originalUpload) { + // If there is original file already present, remove the modified file + $this->files[$field . '/original'][$name]['crop'] = $crop; + $this->removeTmpFile($oldUpload['tmp_name'] ?? ''); + } else { + // Otherwise make the previous file as original + $oldUpload['crop'] = $crop; + $this->files[$field . '/original'][$name] = $oldUpload; + } + } else { + $this->files[$field . '/original'][$name] = [ + 'name' => $name, + 'type' => $data['type'], + 'crop' => $crop + ]; + } + } else { + // Deal with replacing upload + $originalUpload = $this->files[$field . '/original'][$name] ?? null; + $this->files[$field . '/original'][$name] = null; + + $this->removeTmpFile($oldUpload['tmp_name'] ?? ''); + $this->removeTmpFile($originalUpload['tmp_name'] ?? ''); + } + + // Prepare data to be saved later + $this->files[$field][$name] = $data; + } +} diff --git a/system/src/Grav/Framework/Form/FormFlashFile.php b/system/src/Grav/Framework/Form/FormFlashFile.php new file mode 100644 index 0000000..f0c7144 --- /dev/null +++ b/system/src/Grav/Framework/Form/FormFlashFile.php @@ -0,0 +1,266 @@ +id = $flash->getId() ?: $flash->getUniqueId(); + $this->field = $field; + $this->upload = $upload; + $this->flash = $flash; + + $tmpFile = $this->getTmpFile(); + if (!$tmpFile && $this->isOk()) { + $this->upload['error'] = \UPLOAD_ERR_NO_FILE; + } + + if (!isset($this->upload['size'])) { + $this->upload['size'] = $tmpFile && $this->isOk() ? filesize($tmpFile) : 0; + } + } + + /** + * @return StreamInterface + */ + public function getStream() + { + $this->validateActive(); + + $tmpFile = $this->getTmpFile(); + if (null === $tmpFile) { + throw new RuntimeException('No temporary file'); + } + + $resource = fopen($tmpFile, 'rb'); + if (false === $resource) { + throw new RuntimeException('No temporary file'); + } + + return Stream::create($resource); + } + + /** + * @param string $targetPath + * @return void + */ + public function moveTo($targetPath) + { + $this->validateActive(); + + if (!is_string($targetPath) || empty($targetPath)) { + throw new InvalidArgumentException('Invalid path provided for move operation; must be a non-empty string'); + } + $tmpFile = $this->getTmpFile(); + if (null === $tmpFile) { + throw new RuntimeException('No temporary file'); + } + + $this->moved = copy($tmpFile, $targetPath); + + if (false === $this->moved) { + throw new RuntimeException(sprintf('Uploaded file could not be moved to %s', $targetPath)); + } + + $filename = $this->getClientFilename(); + if ($filename) { + $this->flash->removeFile($filename, $this->field); + } + } + + public function getId(): string + { + return $this->id; + } + + /** + * @return string + */ + public function getField(): string + { + return $this->field; + } + + /** + * @return int + */ + public function getSize() + { + return $this->upload['size']; + } + + /** + * @return int + */ + public function getError() + { + return $this->upload['error'] ?? \UPLOAD_ERR_OK; + } + + /** + * @return string + */ + public function getClientFilename() + { + return $this->upload['name'] ?? 'unknown'; + } + + /** + * @return string + */ + public function getClientMediaType() + { + return $this->upload['type'] ?? 'application/octet-stream'; + } + + /** + * @return bool + */ + public function isMoved(): bool + { + return $this->moved; + } + + /** + * @return array + */ + public function getMetaData(): array + { + if (isset($this->upload['crop'])) { + return ['crop' => $this->upload['crop']]; + } + + return []; + } + + /** + * @return string + */ + public function getDestination() + { + return $this->upload['path'] ?? ''; + } + + /** + * @return array + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->upload; + } + + /** + * @return void + */ + public function checkXss(): void + { + $tmpFile = $this->getTmpFile(); + $mime = $this->getClientMediaType(); + if (Utils::contains($mime, 'svg', false)) { + $response = Security::detectXssFromSvgFile($tmpFile); + if ($response) { + throw new RuntimeException(sprintf('SVG file XSS check failed on %s', $response)); + } + } + } + + /** + * @return string|null + */ + public function getTmpFile(): ?string + { + $tmpName = $this->upload['tmp_name'] ?? null; + + if (!$tmpName) { + return null; + } + + $tmpFile = $this->flash->getTmpDir() . '/' . $tmpName; + + return file_exists($tmpFile) ? $tmpFile : null; + } + + /** + * @return array + */ + #[\ReturnTypeWillChange] + public function __debugInfo() + { + return [ + 'id:private' => $this->id, + 'field:private' => $this->field, + 'moved:private' => $this->moved, + 'upload:private' => $this->upload, + ]; + } + + /** + * @return void + * @throws RuntimeException if is moved or not ok + */ + private function validateActive(): void + { + if (!$this->isOk()) { + throw new RuntimeException('Cannot retrieve stream due to upload error'); + } + + if ($this->moved) { + throw new RuntimeException('Cannot retrieve stream after it has already been moved'); + } + + if (!$this->getTmpFile()) { + throw new RuntimeException('Cannot retrieve stream as the file is missing'); + } + } + + /** + * @return bool return true if there is no upload error + */ + private function isOk(): bool + { + return \UPLOAD_ERR_OK === $this->getError(); + } +} diff --git a/system/src/Grav/Framework/Form/Interfaces/FormFactoryInterface.php b/system/src/Grav/Framework/Form/Interfaces/FormFactoryInterface.php new file mode 100644 index 0000000..8076f91 --- /dev/null +++ b/system/src/Grav/Framework/Form/Interfaces/FormFactoryInterface.php @@ -0,0 +1,42 @@ +|Data|null */ + private $data; + /** @var UploadedFileInterface[] */ + private $files = []; + /** @var FormFlashInterface|null */ + private $flash; + /** @var string */ + private $flashFolder; + /** @var Blueprint */ + private $blueprint; + + /** + * @return string + */ + public function getId(): string + { + return $this->id; + } + + /** + * @param string $id + */ + public function setId(string $id): void + { + $this->id = $id; + } + + /** + * @return void + */ + public function disable(): void + { + $this->enabled = false; + } + + /** + * @return void + */ + public function enable(): void + { + $this->enabled = true; + } + + /** + * @return bool + */ + public function isEnabled(): bool + { + return $this->enabled; + } + + /** + * @return string + */ + public function getUniqueId(): string + { + return $this->uniqueid; + } + + /** + * @param string $uniqueId + * @return void + */ + public function setUniqueId(string $uniqueId): void + { + $this->uniqueid = $uniqueId; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @return string + */ + public function getFormName(): string + { + return $this->name; + } + + /** + * @return string + */ + public function getNonceName(): string + { + return 'form-nonce'; + } + + /** + * @return string + */ + public function getNonceAction(): string + { + return 'form'; + } + + /** + * @return string + */ + public function getNonce(): string + { + return Utils::getNonce($this->getNonceAction()); + } + + /** + * @return string + */ + public function getAction(): string + { + return ''; + } + + /** + * @return string + */ + public function getTask(): string + { + return $this->getBlueprint()->get('form/task') ?? ''; + } + + /** + * @param string|null $name + * @return mixed + */ + public function getData(string $name = null) + { + return null !== $name ? $this->data[$name] : $this->data; + } + + /** + * @return array|UploadedFileInterface[] + */ + public function getFiles(): array + { + return $this->files; + } + + /** + * @param string $name + * @return mixed|null + */ + public function getValue(string $name) + { + return $this->data[$name] ?? null; + } + + /** + * @param string $name + * @return mixed|null + */ + public function getDefaultValue(string $name) + { + $path = explode('.', $name); + $offset = array_shift($path); + + $current = $this->getDefaultValues(); + + if (!isset($current[$offset])) { + return null; + } + + $current = $current[$offset]; + + while ($path) { + $offset = array_shift($path); + + if ((is_array($current) || $current instanceof ArrayAccess) && isset($current[$offset])) { + $current = $current[$offset]; + } elseif (is_object($current) && isset($current->{$offset})) { + $current = $current->{$offset}; + } else { + return null; + } + }; + + return $current; + } + + /** + * @return array + */ + public function getDefaultValues(): array + { + return $this->getBlueprint()->getDefaults(); + } + + /** + * @param ServerRequestInterface $request + * @return FormInterface|$this + */ + public function handleRequest(ServerRequestInterface $request): FormInterface + { + // Set current form to be active. + $grav = Grav::instance(); + $forms = $grav['forms'] ?? null; + if ($forms) { + $forms->setActiveForm($this); + + /** @var Twig $twig */ + $twig = $grav['twig']; + $twig->twig_vars['form'] = $this; + } + + try { + [$data, $files] = $this->parseRequest($request); + + $this->submit($data, $files); + } catch (Exception $e) { + /** @var Debugger $debugger */ + $debugger = $grav['debugger']; + $debugger->addException($e); + + $this->setError($e->getMessage()); + } + + return $this; + } + + /** + * @param ServerRequestInterface $request + * @return FormInterface|$this + */ + public function setRequest(ServerRequestInterface $request): FormInterface + { + [$data, $files] = $this->parseRequest($request); + + $this->data = new Data($data, $this->getBlueprint()); + $this->files = $files; + + return $this; + } + + /** + * @return bool + */ + public function isValid(): bool + { + return $this->status === 'success'; + } + + /** + * @return string|null + */ + public function getError(): ?string + { + return !$this->isValid() ? $this->message : null; + } + + /** + * @return array + */ + public function getErrors(): array + { + return !$this->isValid() ? $this->messages : []; + } + + /** + * @return bool + */ + public function isSubmitted(): bool + { + return $this->submitted; + } + + /** + * @return bool + */ + public function validate(): bool + { + if (!$this->isValid()) { + return false; + } + + try { + $this->validateData($this->data); + $this->validateUploads($this->getFiles()); + } catch (ValidationException $e) { + $this->setErrors($e->getMessages()); + } catch (Exception $e) { + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + $debugger->addException($e); + + $this->setError($e->getMessage()); + } + + $this->filterData($this->data); + + return $this->isValid(); + } + + /** + * @param array $data + * @param UploadedFileInterface[]|null $files + * @return FormInterface|$this + */ + public function submit(array $data, array $files = null): FormInterface + { + try { + if ($this->isSubmitted()) { + throw new RuntimeException('Form has already been submitted'); + } + + $this->data = new Data($data, $this->getBlueprint()); + $this->files = $files ?? []; + + if (!$this->validate()) { + return $this; + } + + $this->doSubmit($this->data->toArray(), $this->files); + + $this->submitted = true; + } catch (Exception $e) { + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + $debugger->addException($e); + + $this->setError($e->getMessage()); + } + + return $this; + } + + /** + * @return void + */ + public function reset(): void + { + // Make sure that the flash object gets deleted. + $this->getFlash()->delete(); + + $this->data = null; + $this->files = []; + $this->status = 'success'; + $this->message = null; + $this->messages = []; + $this->submitted = false; + $this->flash = null; + } + + /** + * @return array + */ + public function getFields(): array + { + return $this->getBlueprint()->fields(); + } + + /** + * @return array + */ + public function getButtons(): array + { + return $this->getBlueprint()->get('form/buttons') ?? []; + } + + /** + * @return array + */ + public function getTasks(): array + { + return $this->getBlueprint()->get('form/tasks') ?? []; + } + + /** + * @return Blueprint + */ + abstract public function getBlueprint(): Blueprint; + + /** + * Get form flash object. + * + * @return FormFlashInterface + */ + public function getFlash() + { + if (null === $this->flash) { + $grav = Grav::instance(); + $config = [ + 'session_id' => $this->getSessionId(), + 'unique_id' => $this->getUniqueId(), + 'form_name' => $this->getName(), + 'folder' => $this->getFlashFolder(), + 'id' => $this->getFlashId() + ]; + + $this->flash = new FormFlash($config); + $this->flash->setUrl($grav['uri']->url)->setUser($grav['user'] ?? null); + } + + return $this->flash; + } + + /** + * Get all available form flash objects for this form. + * + * @return FormFlashInterface[] + */ + public function getAllFlashes(): array + { + $folder = $this->getFlashFolder(); + if (!$folder || !is_dir($folder)) { + return []; + } + + $name = $this->getName(); + + $list = []; + /** @var SplFileInfo $file */ + foreach (new FilesystemIterator($folder) as $file) { + $uniqueId = $file->getFilename(); + $config = [ + 'session_id' => $this->getSessionId(), + 'unique_id' => $uniqueId, + 'form_name' => $name, + 'folder' => $this->getFlashFolder(), + 'id' => $this->getFlashId() + ]; + $flash = new FormFlash($config); + if ($flash->exists() && $flash->getFormName() === $name) { + $list[] = $flash; + } + } + + return $list; + } + + /** + * {@inheritdoc} + * @see FormInterface::render() + */ + public function render(string $layout = null, array $context = []) + { + if (null === $layout) { + $layout = 'default'; + } + + $grav = Grav::instance(); + + $block = HtmlBlock::create(); + $block->disableCache(); + + $output = $this->getTemplate($layout)->render( + ['grav' => $grav, 'config' => $grav['config'], 'block' => $block, 'form' => $this, 'layout' => $layout] + $context + ); + + $block->setContent($output); + + return $block; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return $this->doSerialize(); + } + + /** + * @return array + */ + final public function __serialize(): array + { + return $this->doSerialize(); + } + + /** + * @param array $data + * @return void + */ + final public function __unserialize(array $data): void + { + $this->doUnserialize($data); + } + + protected function getSessionId(): string + { + if (null === $this->sessionid) { + /** @var Grav $grav */ + $grav = Grav::instance(); + + /** @var SessionInterface|null $session */ + $session = $grav['session'] ?? null; + + $this->sessionid = $session ? ($session->getId() ?? '') : ''; + } + + return $this->sessionid; + } + + /** + * @param string $sessionId + * @return void + */ + protected function setSessionId(string $sessionId): void + { + $this->sessionid = $sessionId; + } + + /** + * @return void + */ + protected function unsetFlash(): void + { + $this->flash = null; + } + + /** + * @return string|null + */ + protected function getFlashFolder(): ?string + { + $grav = Grav::instance(); + + /** @var UserInterface|null $user */ + $user = $grav['user'] ?? null; + if (null !== $user && $user->exists()) { + $username = $user->username; + $mediaFolder = $user->getMediaFolder(); + } else { + $username = null; + $mediaFolder = null; + } + $session = $grav['session'] ?? null; + $sessionId = $session ? $session->getId() : null; + + // Fill template token keys/value pairs. + $dataMap = [ + '[FORM_NAME]' => $this->getName(), + '[SESSIONID]' => $sessionId ?? '!!', + '[USERNAME]' => $username ?? '!!', + '[USERNAME_OR_SESSIONID]' => $username ?? $sessionId ?? '!!', + '[ACCOUNT]' => $mediaFolder ?? '!!' + ]; + + $flashLookupFolder = $this->getFlashLookupFolder(); + + $path = str_replace(array_keys($dataMap), array_values($dataMap), $flashLookupFolder); + + // Make sure we only return valid paths. + return strpos($path, '!!') === false ? rtrim($path, '/') : null; + } + + /** + * @return string|null + */ + protected function getFlashId(): ?string + { + // Fill template token keys/value pairs. + $dataMap = [ + '[FORM_NAME]' => $this->getName(), + '[SESSIONID]' => 'session', + '[USERNAME]' => '!!', + '[USERNAME_OR_SESSIONID]' => '!!', + '[ACCOUNT]' => 'account' + ]; + + $flashLookupFolder = $this->getFlashLookupFolder(); + + $path = str_replace(array_keys($dataMap), array_values($dataMap), $flashLookupFolder); + + // Make sure we only return valid paths. + return strpos($path, '!!') === false ? rtrim($path, '/') : null; + } + + /** + * @return string + */ + protected function getFlashLookupFolder(): string + { + if (null === $this->flashFolder) { + $this->flashFolder = $this->getBlueprint()->get('form/flash_folder') ?? 'tmp://forms/[SESSIONID]'; + } + + return $this->flashFolder; + } + + /** + * @param string $folder + * @return void + */ + protected function setFlashLookupFolder(string $folder): void + { + $this->flashFolder = $folder; + } + + /** + * Set a single error. + * + * @param string $error + * @return void + */ + protected function setError(string $error): void + { + $this->status = 'error'; + $this->message = $error; + } + + /** + * Set all errors. + * + * @param array $errors + * @return void + */ + protected function setErrors(array $errors): void + { + $this->status = 'error'; + $this->messages = $errors; + } + + /** + * @param string $layout + * @return Template|TemplateWrapper + * @throws LoaderError + * @throws SyntaxError + */ + protected function getTemplate($layout) + { + $grav = Grav::instance(); + + /** @var Twig $twig */ + $twig = $grav['twig']; + + return $twig->twig()->resolveTemplate( + [ + "forms/{$layout}/form.html.twig", + 'forms/default/form.html.twig' + ] + ); + } + + /** + * Parse PSR-7 ServerRequest into data and files. + * + * @param ServerRequestInterface $request + * @return array + */ + protected function parseRequest(ServerRequestInterface $request): array + { + $method = $request->getMethod(); + if (!in_array($method, ['PUT', 'POST', 'PATCH'])) { + throw new RuntimeException(sprintf('FlexForm: Bad HTTP method %s', $method)); + } + + $body = (array)$request->getParsedBody(); + $data = isset($body['data']) ? $this->decodeData($body['data']) : null; + + $flash = $this->getFlash(); + /* + if (null !== $data) { + $flash->setData($data); + $flash->save(); + } + */ + + $blueprint = $this->getBlueprint(); + $includeOriginal = (bool)($blueprint->form()['images']['original'] ?? null); + $files = $flash->getFilesByFields($includeOriginal); + + $data = $blueprint->processForm($data ?? [], $body['toggleable_data'] ?? []); + + return [ + $data, + $files + ]; + } + + /** + * Validate data and throw validation exceptions if validation fails. + * + * @param ArrayAccess|Data|null $data + * @return void + * @throws ValidationException + * @phpstan-param ArrayAccess|Data|null $data + * @throws Exception + */ + protected function validateData($data = null): void + { + if ($data instanceof Data) { + $data->validate(); + } + } + + /** + * Filter validated data. + * + * @param ArrayAccess|Data|null $data + * @return void + * @phpstan-param ArrayAccess|Data|null $data + */ + protected function filterData($data = null): void + { + if ($data instanceof Data) { + $data->filter(); + } + } + + /** + * Validate all uploaded files. + * + * @param array $files + * @return void + */ + protected function validateUploads(array $files): void + { + foreach ($files as $file) { + if (null === $file) { + continue; + } + if ($file instanceof UploadedFileInterface) { + $this->validateUpload($file); + } else { + $this->validateUploads($file); + } + } + } + + /** + * Validate uploaded file. + * + * @param UploadedFileInterface $file + * @return void + */ + protected function validateUpload(UploadedFileInterface $file): void + { + // Handle bad filenames. + $filename = $file->getClientFilename(); + if ($filename && !Utils::checkFilename($filename)) { + $grav = Grav::instance(); + throw new RuntimeException( + sprintf($grav['language']->translate('PLUGIN_FORM.FILEUPLOAD_UNABLE_TO_UPLOAD', null, true), $filename, 'Bad filename') + ); + } + + if ($file instanceof FormFlashFile) { + $file->checkXss(); + } + } + + /** + * Decode POST data + * + * @param array $data + * @return array + */ + protected function decodeData($data): array + { + if (!is_array($data)) { + return []; + } + + // Decode JSON encoded fields and merge them to data. + if (isset($data['_json'])) { + $data = array_replace_recursive($data, $this->jsonDecode($data['_json'])); + + unset($data['_json']); + } + + return $data; + } + + /** + * Recursively JSON decode POST data. + * + * @param array $data + * @return array + */ + protected function jsonDecode(array $data): array + { + foreach ($data as $key => &$value) { + if (is_array($value)) { + $value = $this->jsonDecode($value); + } elseif (trim($value) === '') { + unset($data[$key]); + } else { + $value = json_decode($value, true); + if ($value === null && json_last_error() !== JSON_ERROR_NONE) { + unset($data[$key]); + $this->setError("Badly encoded JSON data (for {$key}) was sent to the form"); + } + } + } + + return $data; + } + + /** + * @return array + */ + protected function doSerialize(): array + { + $data = $this->data instanceof Data ? $this->data->toArray() : null; + + return [ + 'name' => $this->name, + 'id' => $this->id, + 'uniqueid' => $this->uniqueid, + 'submitted' => $this->submitted, + 'status' => $this->status, + 'message' => $this->message, + 'messages' => $this->messages, + 'data' => $data, + 'files' => $this->files, + ]; + } + + /** + * @param array $data + * @return void + */ + protected function doUnserialize(array $data): void + { + $this->name = $data['name']; + $this->id = $data['id']; + $this->uniqueid = $data['uniqueid']; + $this->submitted = $data['submitted'] ?? false; + $this->status = $data['status'] ?? 'success'; + $this->message = $data['message'] ?? null; + $this->messages = $data['messages'] ?? []; + $this->data = isset($data['data']) ? new Data($data['data'], $this->getBlueprint()) : null; + $this->files = $data['files'] ?? []; + } +} diff --git a/system/src/Grav/Framework/Interfaces/RenderInterface.php b/system/src/Grav/Framework/Interfaces/RenderInterface.php new file mode 100644 index 0000000..51807e4 --- /dev/null +++ b/system/src/Grav/Framework/Interfaces/RenderInterface.php @@ -0,0 +1,38 @@ +render('custom', ['variable' => 'value']); + * @example {% render object layout 'custom' with { variable: 'value' } %} + * + * @param string|null $layout Layout to be used. + * @param array $context Extra context given to the renderer. + * + * @return ContentBlockInterface|HtmlBlock Returns `HtmlBlock` containing the rendered output. + * @api + */ + public function render(string $layout = null, array $context = []); +} diff --git a/system/src/Grav/Framework/Logger/Processors/UserProcessor.php b/system/src/Grav/Framework/Logger/Processors/UserProcessor.php new file mode 100644 index 0000000..ffe46d9 --- /dev/null +++ b/system/src/Grav/Framework/Logger/Processors/UserProcessor.php @@ -0,0 +1,34 @@ +exists()) { + $record['extra']['user'] = ['username' => $user->username, 'email' => $user->email]; + } + + return $record; + } +} diff --git a/system/src/Grav/Framework/Media/Interfaces/MediaCollectionInterface.php b/system/src/Grav/Framework/Media/Interfaces/MediaCollectionInterface.php new file mode 100644 index 0000000..69f7acd --- /dev/null +++ b/system/src/Grav/Framework/Media/Interfaces/MediaCollectionInterface.php @@ -0,0 +1,23 @@ + + * @extends Iterator + */ +interface MediaCollectionInterface extends ArrayAccess, Countable, Iterator +{ +} diff --git a/system/src/Grav/Framework/Media/Interfaces/MediaInterface.php b/system/src/Grav/Framework/Media/Interfaces/MediaInterface.php new file mode 100644 index 0000000..39d0501 --- /dev/null +++ b/system/src/Grav/Framework/Media/Interfaces/MediaInterface.php @@ -0,0 +1,37 @@ +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|null $separator Separator, defaults to '.' + * @return mixed Value. + */ + public function get($name, $default = null, $separator = null); +} diff --git a/system/src/Grav/Framework/Media/MediaIdentifier.php b/system/src/Grav/Framework/Media/MediaIdentifier.php new file mode 100644 index 0000000..986e997 --- /dev/null +++ b/system/src/Grav/Framework/Media/MediaIdentifier.php @@ -0,0 +1,150 @@ + + */ +class MediaIdentifier extends Identifier +{ + /** @var MediaObjectInterface|null */ + private $object = null; + + /** + * @param MediaObjectInterface $object + * @return MediaIdentifier + */ + public static function createFromObject(MediaObjectInterface $object): MediaIdentifier + { + $instance = new static($object->getId()); + $instance->setObject($object); + + return $instance; + } + + /** + * @param string $id + */ + public function __construct(string $id) + { + parent::__construct($id, 'media'); + } + + /** + * @return T + */ + public function getObject(): ?MediaObjectInterface + { + if (!isset($this->object)) { + $type = $this->getType(); + $id = $this->getId(); + + $parts = explode('/', $id); + if ($type === 'media' && str_starts_with($id, 'uploads/')) { + array_shift($parts); + [, $folder, $uniqueId, $field, $filename] = $this->findFlash($parts); + + $flash = $this->getFlash($folder, $uniqueId); + if ($flash->exists()) { + + $uploadedFile = $flash->getFilesByField($field)[$filename] ?? null; + + $this->object = UploadedMediaObject::createFromFlash($flash, $field, $filename, $uploadedFile); + } + } else { + $type = array_shift($parts); + $key = array_shift($parts); + $field = array_shift($parts); + $filename = implode('/', $parts); + + $flexObject = $this->getFlexObject($type, $key); + if ($flexObject && method_exists($flexObject, 'getMediaField') && method_exists($flexObject, 'getMedia')) { + $media = $field !== 'media' ? $flexObject->getMediaField($field) : $flexObject->getMedia(); + $image = null; + if ($media) { + $image = $media[$filename]; + } + + $this->object = new MediaObject($field, $filename, $image, $flexObject); + } + } + + if (!isset($this->object)) { + throw new \RuntimeException(sprintf('Object not found for identifier {type: "%s", id: "%s"}', $type, $id)); + } + } + + return $this->object; + } + + /** + * @param T $object + */ + public function setObject(MediaObjectInterface $object): void + { + $type = $this->getType(); + $objectType = $object->getType(); + + if ($type !== $objectType) { + throw new \RuntimeException(sprintf('Object has to be type %s, %s given', $type, $objectType)); + } + + $this->object = $object; + } + + protected function findFlash(array $parts): ?array + { + $type = array_shift($parts); + if ($type === 'account') { + /** @var UserInterface|null $user */ + $user = Grav::instance()['user'] ?? null; + $folder = $user->getMediaFolder(); + } else { + $folder = 'tmp://'; + } + + if (!$folder) { + return null; + } + + do { + $part = array_shift($parts); + $folder .= "/{$part}"; + } while (!str_starts_with($part, 'flex-')); + + $uniqueId = array_shift($parts); + $field = array_shift($parts); + $filename = implode('/', $parts); + + return [$type, $folder, $uniqueId, $field, $filename]; + } + + protected function getFlash(string $folder, string $uniqueId): FlexFormFlash + { + $config = [ + 'unique_id' => $uniqueId, + 'folder' => $folder + ]; + + return new FlexFormFlash($config); + } + + protected function getFlexObject(string $type, string $key): ?FlexObjectInterface + { + /** @var Flex $flex */ + $flex = Grav::instance()['flex']; + + return $flex->getObject($key, $type); + } +} diff --git a/system/src/Grav/Framework/Media/MediaObject.php b/system/src/Grav/Framework/Media/MediaObject.php new file mode 100644 index 0000000..8a438bf --- /dev/null +++ b/system/src/Grav/Framework/Media/MediaObject.php @@ -0,0 +1,215 @@ +field = $field; + $this->filename = $filename; + $this->media = $media; + $this->object = $object; + } + + /** + * @return string + */ + public function getType(): string + { + return 'media'; + } + + /** + * @return string + */ + public function getId(): string + { + $field = $this->field; + $object = $this->object; + $path = $field ? "/{$field}/" : '/media/'; + + return $object->getType() . '/' . $object->getKey() . $path . basename($this->filename); + } + + /** + * @return bool + */ + public function exists(): bool + { + return $this->media !== null; + } + + /** + * @return array + */ + public function getMeta(): array + { + if (!isset($this->media)) { + return []; + } + + return $this->media->getMeta(); + } + + /** + * @param string $field + * @return mixed|null + */ + public function get(string $field) + { + if (!isset($this->media)) { + return null; + } + + return $this->media->get($field); + } + + /** + * @return string + */ + public function getUrl(): string + { + if (!isset($this->media)) { + return ''; + } + + return $this->media->url(); + } + + /** + * Create media response. + * + * @param array $actions + * @return Response + */ + public function createResponse(array $actions): ResponseInterface + { + if (!isset($this->media)) { + return $this->create404Response($actions); + } + + $media = $this->media; + + if ($actions) { + $media = $this->processMediaActions($media, $actions); + } + + // FIXME: This only works for images + if (!$media instanceof ImageMedium) { + throw new \RuntimeException('Not Implemented', 500); + } + + $filename = $media->path(false); + $time = filemtime($filename); + $size = filesize($filename); + $body = fopen($filename, 'rb'); + $headers = [ + 'Content-Type' => $media->get('mime'), + 'Last-Modified' => gmdate('D, d M Y H:i:s', $time) . ' GMT', + 'ETag' => sprintf('%x-%x', $size, $time) + ]; + + return new Response(200, $headers, $body); + } + + /** + * Process media actions + * + * @param GravMediaObjectInterface $medium + * @param array $actions + * @return GravMediaObjectInterface + */ + protected function processMediaActions(GravMediaObjectInterface $medium, array $actions): GravMediaObjectInterface + { + // loop through actions for the image and call them + foreach ($actions as $method => $params) { + $matches = []; + + if (preg_match('/\[(.*)]/', $params, $matches)) { + $args = [explode(',', $matches[1])]; + } else { + $args = explode(',', $params); + } + + try { + $medium->{$method}(...$args); + } catch (Throwable $e) { + // Ignore all errors for now and just skip the action. + } + } + + return $medium; + } + + /** + * @param array $actions + * @return Response + */ + protected function create404Response(array $actions): Response + { + // Display placeholder image. + $filename = static::$placeholderImage; + + $time = filemtime($filename); + $size = filesize($filename); + $body = fopen($filename, 'rb'); + $headers = [ + 'Content-Type' => 'image/svg', + 'Last-Modified' => gmdate('D, d M Y H:i:s', $time) . ' GMT', + 'ETag' => sprintf('%x-%x', $size, $time) + ]; + + return new Response(404, $headers, $body); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'type' => $this->getType(), + 'id' => $this->getId() + ]; + } + + /** + * @return string[] + */ + public function __debugInfo(): array + { + return $this->jsonSerialize(); + } +} diff --git a/system/src/Grav/Framework/Media/UploadedMediaObject.php b/system/src/Grav/Framework/Media/UploadedMediaObject.php new file mode 100644 index 0000000..0fe12e1 --- /dev/null +++ b/system/src/Grav/Framework/Media/UploadedMediaObject.php @@ -0,0 +1,172 @@ +getId(); + + return new static($id, $field, $filename, $uploadedFile); + } + + /** + * @param string $id + * @param string|null $field + * @param string $filename + * @param UploadedFileInterface|null $uploadedFile + */ + public function __construct(string $id, ?string $field, string $filename, ?UploadedFileInterface $uploadedFile = null) + { + $this->id = $id; + $this->field = $field; + $this->filename = $filename; + $this->uploadedFile = $uploadedFile; + if ($uploadedFile) { + $this->meta = [ + 'filename' => $uploadedFile->getClientFilename(), + 'mime' => $uploadedFile->getClientMediaType(), + 'size' => $uploadedFile->getSize() + ]; + } else { + $this->meta = []; + } + } + + /** + * @return string + */ + public function getType(): string + { + return 'media'; + } + + /** + * @return string + */ + public function getId(): string + { + $id = $this->id; + $field = $this->field; + $path = $field ? "/{$field}/" : ''; + + return 'uploads/' . $id . $path . basename($this->filename); + } + + /** + * @return bool + */ + public function exists(): bool + { + //return $this->uploadedFile !== null; + return false; + } + + /** + * @return array + */ + public function getMeta(): array + { + return $this->meta; + } + + /** + * @param string $field + * @return mixed|null + */ + public function get(string $field) + { + return $this->meta[$field] ?? null; + } + + /** + * @return string + */ + public function getUrl(): string + { + return ''; + } + + /** + * @return UploadedFileInterface|null + */ + public function getUploadedFile(): ?UploadedFileInterface + { + return $this->uploadedFile; + } + + /** + * @param array $actions + * @return Response + */ + public function createResponse(array $actions): ResponseInterface + { + // Display placeholder image. + $filename = static::$placeholderImage; + + $time = filemtime($filename); + $size = filesize($filename); + $body = fopen($filename, 'rb'); + $headers = [ + 'Content-Type' => 'image/svg', + 'Last-Modified' => gmdate('D, d M Y H:i:s', $time) . ' GMT', + 'ETag' => sprintf('%x-%x', $size, $time) + ]; + + return new Response(404, $headers, $body); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'type' => $this->getType(), + 'id' => $this->getId() + ]; + } + + /** + * @return string[] + */ + public function __debugInfo(): array + { + return $this->jsonSerialize(); + } +} diff --git a/system/src/Grav/Framework/Mime/MimeTypes.php b/system/src/Grav/Framework/Mime/MimeTypes.php new file mode 100644 index 0000000..9347651 --- /dev/null +++ b/system/src/Grav/Framework/Mime/MimeTypes.php @@ -0,0 +1,107 @@ + ['mime/type', 'mime/type2']] + */ + public static function createFromMimes(array $mimes): self + { + $extensions = []; + foreach ($mimes as $ext => $list) { + foreach ($list as $mime) { + $list = $extensions[$mime] ?? []; + if (!in_array($ext, $list, true)) { + $list[] = $ext; + $extensions[$mime] = $list; + } + } + } + + return new static($extensions, $mimes); + } + + /** + * @param string $extension + * @return string|null + */ + public function getMimeType(string $extension): ?string + { + $extension = $this->cleanInput($extension); + + return $this->mimes[$extension][0] ?? null; + } + + /** + * @param string $mime + * @return string|null + */ + public function getExtension(string $mime): ?string + { + $mime = $this->cleanInput($mime); + + return $this->extensions[$mime][0] ?? null; + } + + /** + * @param string $extension + * @return array + */ + public function getMimeTypes(string $extension): array + { + $extension = $this->cleanInput($extension); + + return $this->mimes[$extension] ?? []; + } + + /** + * @param string $mime + * @return array + */ + public function getExtensions(string $mime): array + { + $mime = $this->cleanInput($mime); + + return $this->extensions[$mime] ?? []; + } + + /** + * @param string $input + * @return string + */ + protected function cleanInput(string $input): string + { + return strtolower(trim($input)); + } + + /** + * @param array $extensions + * @param array $mimes + */ + protected function __construct(array $extensions, array $mimes) + { + $this->extensions = $extensions; + $this->mimes = $mimes; + } +} diff --git a/system/src/Grav/Framework/Object/Access/ArrayAccessTrait.php b/system/src/Grav/Framework/Object/Access/ArrayAccessTrait.php new file mode 100644 index 0000000..ab68667 --- /dev/null +++ b/system/src/Grav/Framework/Object/Access/ArrayAccessTrait.php @@ -0,0 +1,66 @@ +hasProperty($offset); + } + + /** + * Returns the value at specified offset. + * + * @param mixed $offset The offset to retrieve. + * @return mixed Can return all value types. + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return $this->getProperty($offset); + } + + /** + * Assigns a value to the specified offset. + * + * @param mixed $offset The offset to assign the value to. + * @param mixed $value The value to set. + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value) + { + $this->setProperty($offset, $value); + } + + /** + * Unsets an offset. + * + * @param mixed $offset The offset to unset. + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetUnset($offset) + { + $this->unsetProperty($offset); + } +} diff --git a/system/src/Grav/Framework/Object/Access/NestedArrayAccessTrait.php b/system/src/Grav/Framework/Object/Access/NestedArrayAccessTrait.php new file mode 100644 index 0000000..ee224ad --- /dev/null +++ b/system/src/Grav/Framework/Object/Access/NestedArrayAccessTrait.php @@ -0,0 +1,66 @@ +hasNestedProperty($offset); + } + + /** + * Returns the value at specified offset. + * + * @param mixed $offset The offset to retrieve. + * @return mixed Can return all value types. + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return $this->getNestedProperty($offset); + } + + /** + * Assigns a value to the specified offset. + * + * @param mixed $offset The offset to assign the value to. + * @param mixed $value The value to set. + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value) + { + $this->setNestedProperty($offset, $value); + } + + /** + * Unsets an offset. + * + * @param mixed $offset The offset to unset. + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetUnset($offset) + { + $this->unsetNestedProperty($offset); + } +} diff --git a/system/src/Grav/Framework/Object/Access/NestedPropertyCollectionTrait.php b/system/src/Grav/Framework/Object/Access/NestedPropertyCollectionTrait.php new file mode 100644 index 0000000..5e591f1 --- /dev/null +++ b/system/src/Grav/Framework/Object/Access/NestedPropertyCollectionTrait.php @@ -0,0 +1,120 @@ +getIterator() as $id => $element) { + $list[$id] = $element->hasNestedProperty($property, $separator); + } + + return $list; + } + + /** + * @param string $property Object property to be fetched. + * @param mixed $default Default value if not set. + * @param string|null $separator Separator, defaults to '.' + * @return array Key/Value pairs of the properties. + */ + public function getNestedProperty($property, $default = null, $separator = null) + { + $list = []; + + /** @var NestedObjectInterface $element */ + foreach ($this->getIterator() as $id => $element) { + $list[$id] = $element->getNestedProperty($property, $default, $separator); + } + + return $list; + } + + /** + * @param string $property Object property to be updated. + * @param mixed $value New value. + * @param string|null $separator Separator, defaults to '.' + * @return $this + */ + public function setNestedProperty($property, $value, $separator = null) + { + /** @var NestedObjectInterface $element */ + foreach ($this->getIterator() as $element) { + $element->setNestedProperty($property, $value, $separator); + } + + return $this; + } + + /** + * @param string $property Object property to be updated. + * @param string|null $separator Separator, defaults to '.' + * @return $this + */ + public function unsetNestedProperty($property, $separator = null) + { + /** @var NestedObjectInterface $element */ + foreach ($this->getIterator() as $element) { + $element->unsetNestedProperty($property, $separator); + } + + return $this; + } + + /** + * @param string $property Object property to be updated. + * @param string $default Default value. + * @param string|null $separator Separator, defaults to '.' + * @return $this + */ + public function defNestedProperty($property, $default, $separator = null) + { + /** @var NestedObjectInterface $element */ + foreach ($this->getIterator() as $element) { + $element->defNestedProperty($property, $default, $separator); + } + + return $this; + } + + /** + * Group items in the collection by a field. + * + * @param string $property Object property to be used to make groups. + * @param string|null $separator Separator, defaults to '.' + * @return array + */ + public function group($property, $separator = null) + { + $list = []; + + /** @var NestedObjectInterface $element */ + foreach ($this->getIterator() as $element) { + $list[(string) $element->getNestedProperty($property, null, $separator)][] = $element; + } + + return $list; + } +} diff --git a/system/src/Grav/Framework/Object/Access/NestedPropertyTrait.php b/system/src/Grav/Framework/Object/Access/NestedPropertyTrait.php new file mode 100644 index 0000000..83b94b5 --- /dev/null +++ b/system/src/Grav/Framework/Object/Access/NestedPropertyTrait.php @@ -0,0 +1,180 @@ +getNestedProperty($property, $test, $separator) !== $test; + } + + /** + * @param string $property Object property to be fetched. + * @param mixed|null $default Default value if property has not been set. + * @param string|null $separator Separator, defaults to '.' + * @return mixed Property value. + */ + public function getNestedProperty($property, $default = null, $separator = null) + { + $separator = $separator ?: '.'; + $path = explode($separator, (string) $property); + $offset = array_shift($path); + + if (!$this->hasProperty($offset)) { + return $default; + } + + $current = $this->getProperty($offset); + + while ($path) { + // Get property of nested Object. + if ($current instanceof ObjectInterface) { + if (method_exists($current, 'getNestedProperty')) { + return $current->getNestedProperty(implode($separator, $path), $default, $separator); + } + return $current->getProperty(implode($separator, $path), $default); + } + + $offset = array_shift($path); + + if ((is_array($current) || is_a($current, 'ArrayAccess')) && isset($current[$offset])) { + $current = $current[$offset]; + } elseif (is_object($current) && isset($current->{$offset})) { + $current = $current->{$offset}; + } else { + return $default; + } + }; + + return $current; + } + + + /** + * @param string $property Object property to be updated. + * @param mixed $value New value. + * @param string|null $separator Separator, defaults to '.' + * @return $this + * @throws RuntimeException + */ + public function setNestedProperty($property, $value, $separator = null) + { + $separator = $separator ?: '.'; + $path = explode($separator, $property); + $offset = array_shift($path); + + if (!$path) { + $this->setProperty($offset, $value); + + return $this; + } + + $current = &$this->doGetProperty($offset, null, true); + + while ($path) { + $offset = array_shift($path); + + // Handle arrays and scalars. + if ($current === null) { + $current = [$offset => []]; + } elseif (is_array($current)) { + if (!isset($current[$offset])) { + $current[$offset] = []; + } + } else { + throw new RuntimeException("Cannot set nested property {$property} on non-array value"); + } + + $current = &$current[$offset]; + }; + + $current = $value; + + return $this; + } + + /** + * @param string $property Object property to be updated. + * @param string|null $separator Separator, defaults to '.' + * @return $this + * @throws RuntimeException + */ + public function unsetNestedProperty($property, $separator = null) + { + $separator = $separator ?: '.'; + $path = explode($separator, $property); + $offset = array_shift($path); + + if (!$path) { + $this->unsetProperty($offset); + + return $this; + } + + $last = array_pop($path); + $current = &$this->doGetProperty($offset, null, true); + + while ($path) { + $offset = array_shift($path); + + // Handle arrays and scalars. + if ($current === null) { + return $this; + } + if (is_array($current)) { + if (!isset($current[$offset])) { + return $this; + } + } else { + throw new RuntimeException("Cannot unset nested property {$property} on non-array value"); + } + + $current = &$current[$offset]; + }; + + unset($current[$last]); + + return $this; + } + + /** + * @param string $property Object property to be updated. + * @param mixed $default Default value. + * @param string|null $separator Separator, defaults to '.' + * @return $this + * @throws RuntimeException + */ + public function defNestedProperty($property, $default, $separator = null) + { + if (!$this->hasNestedProperty($property, $separator)) { + $this->setNestedProperty($property, $default, $separator); + } + + return $this; + } +} diff --git a/system/src/Grav/Framework/Object/Access/OverloadedPropertyTrait.php b/system/src/Grav/Framework/Object/Access/OverloadedPropertyTrait.php new file mode 100644 index 0000000..c8e47bc --- /dev/null +++ b/system/src/Grav/Framework/Object/Access/OverloadedPropertyTrait.php @@ -0,0 +1,66 @@ +hasProperty($offset); + } + + /** + * Returns the value at specified offset. + * + * @param mixed $offset The offset to retrieve. + * @return mixed Can return all value types. + */ + #[\ReturnTypeWillChange] + public function __get($offset) + { + return $this->getProperty($offset); + } + + /** + * Assigns a value to the specified offset. + * + * @param mixed $offset The offset to assign the value to. + * @param mixed $value The value to set. + * @return void + */ + #[\ReturnTypeWillChange] + public function __set($offset, $value) + { + $this->setProperty($offset, $value); + } + + /** + * Magic method to unset the attribute + * + * @param mixed $offset The name value to unset + * @return void + */ + #[\ReturnTypeWillChange] + public function __unset($offset) + { + $this->unsetProperty($offset); + } +} diff --git a/system/src/Grav/Framework/Object/ArrayObject.php b/system/src/Grav/Framework/Object/ArrayObject.php new file mode 100644 index 0000000..c5ad0bc --- /dev/null +++ b/system/src/Grav/Framework/Object/ArrayObject.php @@ -0,0 +1,31 @@ + + */ +class ArrayObject implements NestedObjectInterface, ArrayAccess +{ + use ObjectTrait; + use ArrayPropertyTrait; + use NestedPropertyTrait; + use OverloadedPropertyTrait; + use NestedArrayAccessTrait; +} diff --git a/system/src/Grav/Framework/Object/Base/ObjectCollectionTrait.php b/system/src/Grav/Framework/Object/Base/ObjectCollectionTrait.php new file mode 100644 index 0000000..36c7329 --- /dev/null +++ b/system/src/Grav/Framework/Object/Base/ObjectCollectionTrait.php @@ -0,0 +1,377 @@ +getTypePrefix() : ''; + + if (static::$type) { + return $type . static::$type; + } + + $class = get_class($this); + + return $type . strtolower(substr($class, strrpos($class, '\\') + 1)); + } + + /** + * @return string + */ + public function getKey() + { + return $this->_key ?: $this->getType() . '@@' . spl_object_hash($this); + } + + /** + * @return bool + */ + public function hasKey() + { + return !empty($this->_key); + } + + /** + * @param string $property Object property name. + * @return bool[] True if property has been defined (can be null). + */ + public function hasProperty($property) + { + return $this->doHasProperty($property); + } + + /** + * @param string $property Object property to be fetched. + * @param mixed $default Default value if property has not been set. + * @return mixed[] Property values. + */ + public function getProperty($property, $default = null) + { + return $this->doGetProperty($property, $default); + } + + /** + * @param string $property Object property to be updated. + * @param mixed $value New value. + * @return $this + */ + public function setProperty($property, $value) + { + $this->doSetProperty($property, $value); + + return $this; + } + + /** + * @param string $property Object property to be unset. + * @return $this + */ + public function unsetProperty($property) + { + $this->doUnsetProperty($property); + + return $this; + } + + /** + * @param string $property Object property to be defined. + * @param mixed $default Default value. + * @return $this + */ + public function defProperty($property, $default) + { + if (!$this->hasProperty($property)) { + $this->setProperty($property, $default); + } + + return $this; + } + + /** + * @return array + */ + final public function __serialize(): array + { + return $this->doSerialize(); + } + + /** + * @param array $data + * @return void + */ + final public function __unserialize(array $data): void + { + if (method_exists($this, 'initObjectProperties')) { + $this->initObjectProperties(); + } + + $this->doUnserialize($data); + } + + + /** + * @return array + */ + protected function doSerialize() + { + return [ + 'key' => $this->getKey(), + 'type' => $this->getType(), + 'elements' => $this->getElements() + ]; + } + + /** + * @param array $data + * @return void + */ + protected function doUnserialize(array $data) + { + if (!isset($data['key'], $data['type'], $data['elements']) || $data['type'] !== $this->getType()) { + throw new InvalidArgumentException("Cannot unserialize '{$this->getType()}': Bad data"); + } + + $this->setKey($data['key']); + $this->setElements($data['elements']); + } + + /** + * Implements JsonSerializable interface. + * + * @return array + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->doSerialize(); + } + + /** + * Returns a string representation of this object. + * + * @return string + */ + #[\ReturnTypeWillChange] + public function __toString() + { + return $this->getKey(); + } + + /** + * @param string $key + * @return $this + */ + public function setKey($key) + { + $this->_key = (string) $key; + + return $this; + } + + /** + * Create a copy from this collection by cloning all objects in the collection. + * + * @return static + */ + public function copy() + { + $list = []; + foreach ($this->getIterator() as $key => $value) { + /** @phpstan-ignore-next-line */ + $list[$key] = is_object($value) ? clone $value : $value; + } + + /** @phpstan-var static */ + return $this->createFrom($list); + } + + /** + * @return string[] + */ + public function getObjectKeys() + { + return $this->call('getKey'); + } + + /** + * @param string $property Object property to be matched. + * @return bool[] Key/Value pairs of the properties. + */ + public function doHasProperty($property) + { + $list = []; + + /** @var ObjectInterface $element */ + foreach ($this->getIterator() as $id => $element) { + $list[$id] = (bool)$element->hasProperty($property); + } + + return $list; + } + + /** + * @param string $property Object property to be fetched. + * @param mixed $default Default value if not set. + * @param bool $doCreate Not being used. + * @return mixed[] Key/Value pairs of the properties. + */ + public function &doGetProperty($property, $default = null, $doCreate = false) + { + $list = []; + + /** @var ObjectInterface $element */ + foreach ($this->getIterator() as $id => $element) { + $list[$id] = $element->getProperty($property, $default); + } + + return $list; + } + + /** + * @param string $property Object property to be updated. + * @param mixed $value New value. + * @return $this + */ + public function doSetProperty($property, $value) + { + /** @var ObjectInterface $element */ + foreach ($this->getIterator() as $element) { + $element->setProperty($property, $value); + } + + return $this; + } + + /** + * @param string $property Object property to be updated. + * @return $this + */ + public function doUnsetProperty($property) + { + /** @var ObjectInterface $element */ + foreach ($this->getIterator() as $element) { + $element->unsetProperty($property); + } + + return $this; + } + + /** + * @param string $property Object property to be updated. + * @param mixed $default Default value. + * @return $this + */ + public function doDefProperty($property, $default) + { + /** @var ObjectInterface $element */ + foreach ($this->getIterator() as $element) { + $element->defProperty($property, $default); + } + + return $this; + } + + /** + * @param string $method Method name. + * @param array $arguments List of arguments passed to the function. + * @return mixed[] Return values. + */ + public function call($method, array $arguments = []) + { + $list = []; + + /** + * @var string|int $id + * @var ObjectInterface $element + */ + foreach ($this->getIterator() as $id => $element) { + $callable = [$element, $method]; + $list[$id] = is_callable($callable) ? call_user_func_array($callable, $arguments) : null; + } + + return $list; + } + + /** + * Group items in the collection by a field and return them as associated array. + * + * @param string $property + * @return array + * @phpstan-return array + */ + public function group($property) + { + $list = []; + + /** @var ObjectInterface $element */ + foreach ($this->getIterator() as $element) { + $list[(string) $element->getProperty($property)][] = $element; + } + + return $list; + } + + /** + * Group items in the collection by a field and return them as associated array of collections. + * + * @param string $property + * @return static[] + * @phpstan-return array> + */ + public function collectionGroup($property) + { + $collections = []; + foreach ($this->group($property) as $id => $elements) { + /** @phpstan-var static $collection */ + $collection = $this->createFrom($elements); + + $collections[$id] = $collection; + } + + return $collections; + } +} diff --git a/system/src/Grav/Framework/Object/Base/ObjectTrait.php b/system/src/Grav/Framework/Object/Base/ObjectTrait.php new file mode 100644 index 0000000..215a3be --- /dev/null +++ b/system/src/Grav/Framework/Object/Base/ObjectTrait.php @@ -0,0 +1,202 @@ +getTypePrefix() : ''; + + if (static::$type) { + return $type . static::$type; + } + + $class = get_class($this); + return $type . strtolower(substr($class, strrpos($class, '\\') + 1)); + } + + /** + * @return string + */ + public function getKey() + { + return $this->_key ?: $this->getType() . '@@' . spl_object_hash($this); + } + + /** + * @return bool + */ + public function hasKey() + { + return !empty($this->_key); + } + + /** + * @param string $property Object property name. + * @return bool True if property has been defined (can be null). + */ + public function hasProperty($property) + { + return $this->doHasProperty($property); + } + + /** + * @param string $property Object property to be fetched. + * @param mixed $default Default value if property has not been set. + * @return mixed Property value. + */ + public function getProperty($property, $default = null) + { + return $this->doGetProperty($property, $default); + } + + /** + * @param string $property Object property to be updated. + * @param mixed $value New value. + * @return $this + */ + public function setProperty($property, $value) + { + $this->doSetProperty($property, $value); + + return $this; + } + + /** + * @param string $property Object property to be unset. + * @return $this + */ + public function unsetProperty($property) + { + $this->doUnsetProperty($property); + + return $this; + } + + /** + * @param string $property Object property to be defined. + * @param mixed $default Default value. + * @return $this + */ + public function defProperty($property, $default) + { + if (!$this->hasProperty($property)) { + $this->setProperty($property, $default); + } + + return $this; + } + + /** + * @return array + */ + final public function __serialize(): array + { + return $this->doSerialize(); + } + + /** + * @param array $data + * @return void + */ + final public function __unserialize(array $data): void + { + if (method_exists($this, 'initObjectProperties')) { + $this->initObjectProperties(); + } + + $this->doUnserialize($data); + } + + /** + * @return array + */ + protected function doSerialize() + { + return ['key' => $this->getKey(), 'type' => $this->getType(), 'elements' => $this->getElements()]; + } + + /** + * @param array $serialized + * @return void + */ + protected function doUnserialize(array $serialized) + { + if (!isset($serialized['key'], $serialized['type'], $serialized['elements']) || $serialized['type'] !== $this->getType()) { + throw new InvalidArgumentException("Cannot unserialize '{$this->getType()}': Bad data"); + } + + $this->setKey($serialized['key']); + $this->setElements($serialized['elements']); + } + + /** + * Implements JsonSerializable interface. + * + * @return array + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->doSerialize(); + } + + /** + * Returns a string representation of this object. + * + * @return string + */ + #[\ReturnTypeWillChange] + public function __toString() + { + return $this->getKey(); + } + + /** + * @param string $key + * @return $this + */ + protected function setKey($key) + { + $this->_key = (string) $key; + + return $this; + } +} diff --git a/system/src/Grav/Framework/Object/Collection/ObjectExpressionVisitor.php b/system/src/Grav/Framework/Object/Collection/ObjectExpressionVisitor.php new file mode 100644 index 0000000..0103817 --- /dev/null +++ b/system/src/Grav/Framework/Object/Collection/ObjectExpressionVisitor.php @@ -0,0 +1,240 @@ +{$accessor}(); + break; + } + } + + if ($op) { + $function = 'filter' . ucfirst(strtolower($op)); + if (method_exists(static::class, $function)) { + $value = static::$function($value); + } + } + + return $value; + } + + /** + * @param string $str + * @return string + */ + public static function filterLower($str) + { + return mb_strtolower($str); + } + + /** + * @param string $str + * @return string + */ + public static function filterUpper($str) + { + return mb_strtoupper($str); + } + + /** + * @param string $str + * @return int + */ + public static function filterLength($str) + { + return mb_strlen($str); + } + + /** + * @param string $str + * @return string + */ + public static function filterLtrim($str) + { + return ltrim($str); + } + + /** + * @param string $str + * @return string + */ + public static function filterRtrim($str) + { + return rtrim($str); + } + + /** + * @param string $str + * @return string + */ + public static function filterTrim($str) + { + return trim($str); + } + + /** + * Helper for sorting arrays of objects based on multiple fields + orientations. + * + * Comparison between two strings is natural and case insensitive. + * + * @param string $name + * @param int $orientation + * @param Closure|null $next + * + * @return Closure + */ + public static function sortByField($name, $orientation = 1, Closure $next = null) + { + if (!$next) { + $next = function ($a, $b) { + return 0; + }; + } + + return function ($a, $b) use ($name, $next, $orientation) { + $aValue = static::getObjectFieldValue($a, $name); + $bValue = static::getObjectFieldValue($b, $name); + + if ($aValue === $bValue) { + return $next($a, $b); + } + + // For strings we use natural case insensitive sorting. + if (is_string($aValue) && is_string($bValue)) { + return strnatcasecmp($aValue, $bValue) * $orientation; + } + + return (($aValue > $bValue) ? 1 : -1) * $orientation; + }; + } + + /** + * {@inheritDoc} + */ + public function walkComparison(Comparison $comparison) + { + $field = $comparison->getField(); + $value = $comparison->getValue()->getValue(); // shortcut for walkValue() + + switch ($comparison->getOperator()) { + case Comparison::EQ: + return function ($object) use ($field, $value) { + return static::getObjectFieldValue($object, $field) === $value; + }; + + case Comparison::NEQ: + return function ($object) use ($field, $value) { + return static::getObjectFieldValue($object, $field) !== $value; + }; + + case Comparison::LT: + return function ($object) use ($field, $value) { + return static::getObjectFieldValue($object, $field) < $value; + }; + + case Comparison::LTE: + return function ($object) use ($field, $value) { + return static::getObjectFieldValue($object, $field) <= $value; + }; + + case Comparison::GT: + return function ($object) use ($field, $value) { + return static::getObjectFieldValue($object, $field) > $value; + }; + + case Comparison::GTE: + return function ($object) use ($field, $value) { + return static::getObjectFieldValue($object, $field) >= $value; + }; + + case Comparison::IN: + return function ($object) use ($field, $value) { + return in_array(static::getObjectFieldValue($object, $field), $value, true); + }; + + case Comparison::NIN: + return function ($object) use ($field, $value) { + return !in_array(static::getObjectFieldValue($object, $field), $value, true); + }; + + case Comparison::CONTAINS: + return function ($object) use ($field, $value) { + return false !== strpos(static::getObjectFieldValue($object, $field), $value); + }; + + case Comparison::MEMBER_OF: + return function ($object) use ($field, $value) { + $fieldValues = static::getObjectFieldValue($object, $field); + if (!is_array($fieldValues)) { + $fieldValues = iterator_to_array($fieldValues); + } + return in_array($value, $fieldValues, true); + }; + + case Comparison::STARTS_WITH: + return function ($object) use ($field, $value) { + return 0 === strpos(static::getObjectFieldValue($object, $field), $value); + }; + + case Comparison::ENDS_WITH: + return function ($object) use ($field, $value) { + return $value === substr(static::getObjectFieldValue($object, $field), -strlen($value)); + }; + + default: + throw new RuntimeException('Unknown comparison operator: ' . $comparison->getOperator()); + } + } +} diff --git a/system/src/Grav/Framework/Object/Identifiers/Identifier.php b/system/src/Grav/Framework/Object/Identifiers/Identifier.php new file mode 100644 index 0000000..69f41d2 --- /dev/null +++ b/system/src/Grav/Framework/Object/Identifiers/Identifier.php @@ -0,0 +1,66 @@ +id = $id; + $this->type = $type; + } + + /** + * @return string + * @phpstan-pure + */ + public function getId(): string + { + return $this->id; + } + + /** + * @return string + * @phpstan-pure + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'type' => $this->type, + 'id' => $this->id + ]; + } + + /** + * @return array + */ + public function __debugInfo(): array + { + return $this->jsonSerialize(); + } +} diff --git a/system/src/Grav/Framework/Object/Interfaces/NestedObjectCollectionInterface.php b/system/src/Grav/Framework/Object/Interfaces/NestedObjectCollectionInterface.php new file mode 100644 index 0000000..e84423e --- /dev/null +++ b/system/src/Grav/Framework/Object/Interfaces/NestedObjectCollectionInterface.php @@ -0,0 +1,64 @@ + + */ +interface NestedObjectCollectionInterface extends ObjectCollectionInterface +{ + /** + * @param string $property Object property name. + * @param string|null $separator Separator, defaults to '.' + * @return bool[] List of [key => bool] pairs. + */ + public function hasNestedProperty($property, $separator = null); + + /** + * @param string $property Object property to be fetched. + * @param mixed|null $default Default value if property has not been set. + * @param string|null $separator Separator, defaults to '.' + * @return mixed[] List of [key => value] pairs. + */ + public function getNestedProperty($property, $default = null, $separator = null); + + /** + * @param string $property Object property to be updated. + * @param mixed $value New value. + * @param string|null $separator Separator, defaults to '.' + * @return $this + * @throws RuntimeException + */ + public function setNestedProperty($property, $value, $separator = null); + + /** + * @param string $property Object property to be defined. + * @param mixed $default Default value. + * @param string|null $separator Separator, defaults to '.' + * @return $this + * @throws RuntimeException + */ + public function defNestedProperty($property, $default, $separator = null); + + /** + * @param string $property Object property to be unset. + * @param string|null $separator Separator, defaults to '.' + * @return $this + * @throws RuntimeException + */ + public function unsetNestedProperty($property, $separator = null); +} diff --git a/system/src/Grav/Framework/Object/Interfaces/NestedObjectInterface.php b/system/src/Grav/Framework/Object/Interfaces/NestedObjectInterface.php new file mode 100644 index 0000000..61d4d5c --- /dev/null +++ b/system/src/Grav/Framework/Object/Interfaces/NestedObjectInterface.php @@ -0,0 +1,60 @@ + + * @extends Selectable + */ +interface ObjectCollectionInterface extends CollectionInterface, Selectable, Serializable +{ + /** + * @return string + */ + public function getType(); + + /** + * @return string + */ + public function getKey(); + + /** + * @param string $key + * @return $this + */ + public function setKey($key); + + /** + * @param string $property Object property name. + * @return bool[] List of [key => bool] pairs. + */ + public function hasProperty($property); + + /** + * @param string $property Object property to be fetched. + * @param mixed|null $default Default value if property has not been set. + * @return mixed[] List of [key => value] pairs. + */ + public function getProperty($property, $default = null); + + /** + * @param string $property Object property to be updated. + * @param mixed $value New value. + * @return $this + */ + public function setProperty($property, $value); + + /** + * @param string $property Object property to be defined. + * @param mixed $default Default value. + * @return $this + */ + public function defProperty($property, $default); + + /** + * @param string $property Object property to be unset. + * @return $this + */ + public function unsetProperty($property); + + /** + * Create a copy from this collection by cloning all objects in the collection. + * + * @return static + * @phpstan-return static + */ + public function copy(); + + /** + * @return array + */ + public function getObjectKeys(); + + /** + * @param string $name Method name. + * @param array $arguments List of arguments passed to the function. + * @return array Return values. + */ + public function call($name, array $arguments = []); + + /** + * Group items in the collection by a field and return them as associated array. + * + * @param string $property + * @return array + */ + public function group($property); + + /** + * Group items in the collection by a field and return them as associated array of collections. + * + * @param string $property + * @return static[] + * @phpstan-return array> + */ + public function collectionGroup($property); + + /** + * @param array $ordering + * @return ObjectCollectionInterface + * @phpstan-return static + */ + public function orderBy(array $ordering); + + /** + * @param int $start + * @param int|null $limit + * @return ObjectCollectionInterface + * @phpstan-return static + */ + public function limit($start, $limit = null); +} diff --git a/system/src/Grav/Framework/Object/Interfaces/ObjectInterface.php b/system/src/Grav/Framework/Object/Interfaces/ObjectInterface.php new file mode 100644 index 0000000..042f500 --- /dev/null +++ b/system/src/Grav/Framework/Object/Interfaces/ObjectInterface.php @@ -0,0 +1,63 @@ + + */ +class LazyObject implements NestedObjectInterface, ArrayAccess +{ + use ObjectTrait; + use LazyPropertyTrait; + use NestedPropertyTrait; + use OverloadedPropertyTrait; + use NestedArrayAccessTrait; +} diff --git a/system/src/Grav/Framework/Object/ObjectCollection.php b/system/src/Grav/Framework/Object/ObjectCollection.php new file mode 100644 index 0000000..0060e0a --- /dev/null +++ b/system/src/Grav/Framework/Object/ObjectCollection.php @@ -0,0 +1,131 @@ + + * @implements NestedObjectCollectionInterface + */ +class ObjectCollection extends ArrayCollection implements NestedObjectCollectionInterface +{ + /** @phpstan-use ObjectCollectionTrait */ + use ObjectCollectionTrait; + use NestedPropertyCollectionTrait { + NestedPropertyCollectionTrait::group insteadof ObjectCollectionTrait; + } + + /** + * @param array $elements + * @param string|null $key + * @throws InvalidArgumentException + */ + public function __construct(array $elements = [], $key = null) + { + parent::__construct($this->setElements($elements)); + + $this->setKey($key ?? ''); + } + + /** + * @param array $ordering + * @return static + * @phpstan-return static + */ + public function orderBy(array $ordering) + { + $criteria = Criteria::create()->orderBy($ordering); + + return $this->matching($criteria); + } + + /** + * @param int $start + * @param int|null $limit + * @return static + * @phpstan-return static + */ + public function limit($start, $limit = null) + { + /** @phpstan-var static */ + return $this->createFrom($this->slice($start, $limit)); + } + + /** + * @param Criteria $criteria + * @return static + * @phpstan-return static + */ + public function matching(Criteria $criteria) + { + $expr = $criteria->getWhereExpression(); + $filtered = $this->getElements(); + + if ($expr) { + $visitor = new ObjectExpressionVisitor(); + $filter = $visitor->dispatch($expr); + $filtered = array_filter($filtered, $filter); + } + + if ($orderings = $criteria->getOrderings()) { + $next = null; + foreach (array_reverse($orderings) as $field => $ordering) { + $next = ObjectExpressionVisitor::sortByField($field, $ordering === Criteria::DESC ? -1 : 1, $next); + } + + /** @phpstan-ignore-next-line */ + if ($next) { + uasort($filtered, $next); + } + } + + $offset = $criteria->getFirstResult(); + $length = $criteria->getMaxResults(); + + if ($offset || $length) { + $filtered = array_slice($filtered, (int)$offset, $length); + } + + /** @phpstan-var static */ + return $this->createFrom($filtered); + } + + /** + * @return array + * @phpstan-return array + */ + protected function getElements() + { + return $this->toArray(); + } + + /** + * @param array $elements + * @return array + * @phpstan-return array + */ + protected function setElements(array $elements) + { + /** @phpstan-var array $elements */ + return $elements; + } +} diff --git a/system/src/Grav/Framework/Object/ObjectIndex.php b/system/src/Grav/Framework/Object/ObjectIndex.php new file mode 100644 index 0000000..2ec9470 --- /dev/null +++ b/system/src/Grav/Framework/Object/ObjectIndex.php @@ -0,0 +1,281 @@ + + * @implements NestedObjectCollectionInterface + */ +abstract class ObjectIndex extends AbstractIndexCollection implements NestedObjectCollectionInterface +{ + /** @var string */ + protected static $type; + + /** @var string */ + protected $_key; + + /** + * @param bool $prefix + * @return string + */ + public function getType($prefix = true) + { + $type = $prefix ? $this->getTypePrefix() : ''; + + if (static::$type) { + return $type . static::$type; + } + + $class = get_class($this); + return $type . strtolower(substr($class, strrpos($class, '\\') + 1)); + } + + /** + * @return string + */ + public function getKey() + { + return $this->_key ?: $this->getType() . '@@' . spl_object_hash($this); + } + + /** + * @param string $key + * @return $this + */ + public function setKey($key) + { + $this->_key = $key; + + return $this; + } + + /** + * @param string $property Object property name. + * @return bool[] True if property has been defined (can be null). + */ + public function hasProperty($property) + { + return $this->__call('hasProperty', [$property]); + } + + /** + * @param string $property Object property to be fetched. + * @param mixed $default Default value if property has not been set. + * @return mixed[] Property values. + */ + public function getProperty($property, $default = null) + { + return $this->__call('getProperty', [$property, $default]); + } + + /** + * @param string $property Object property to be updated. + * @param string $value New value. + * @return ObjectCollectionInterface + * @phpstan-return C + */ + public function setProperty($property, $value) + { + return $this->__call('setProperty', [$property, $value]); + } + + /** + * @param string $property Object property to be defined. + * @param mixed $default Default value. + * @return ObjectCollectionInterface + * @phpstan-return C + */ + public function defProperty($property, $default) + { + return $this->__call('defProperty', [$property, $default]); + } + + /** + * @param string $property Object property to be unset. + * @return ObjectCollectionInterface + * @phpstan-return C + */ + public function unsetProperty($property) + { + return $this->__call('unsetProperty', [$property]); + } + + /** + * @param string $property Object property name. + * @param string|null $separator Separator, defaults to '.' + * @return bool[] True if property has been defined (can be null). + */ + public function hasNestedProperty($property, $separator = null) + { + return $this->__call('hasNestedProperty', [$property, $separator]); + } + + /** + * @param string $property Object property to be fetched. + * @param mixed $default Default value if property has not been set. + * @param string|null $separator Separator, defaults to '.' + * @return mixed[] Property values. + */ + public function getNestedProperty($property, $default = null, $separator = null) + { + return $this->__call('getNestedProperty', [$property, $default, $separator]); + } + + /** + * @param string $property Object property to be updated. + * @param mixed $value New value. + * @param string|null $separator Separator, defaults to '.' + * @return ObjectCollectionInterface + * @phpstan-return C + */ + public function setNestedProperty($property, $value, $separator = null) + { + return $this->__call('setNestedProperty', [$property, $value, $separator]); + } + + /** + * @param string $property Object property to be defined. + * @param mixed $default Default value. + * @param string|null $separator Separator, defaults to '.' + * @return ObjectCollectionInterface + * @phpstan-return C + */ + public function defNestedProperty($property, $default, $separator = null) + { + return $this->__call('defNestedProperty', [$property, $default, $separator]); + } + + /** + * @param string $property Object property to be unset. + * @param string|null $separator Separator, defaults to '.' + * @return ObjectCollectionInterface + * @phpstan-return C + */ + public function unsetNestedProperty($property, $separator = null) + { + return $this->__call('unsetNestedProperty', [$property, $separator]); + } + + /** + * Create a copy from this collection by cloning all objects in the collection. + * + * @return static + * @return static + */ + public function copy() + { + $list = []; + foreach ($this->getIterator() as $key => $value) { + /** @phpstan-ignore-next-line */ + $list[$key] = is_object($value) ? clone $value : $value; + } + + /** @phpstan-var static */ + return $this->createFrom($list); + } + + /** + * @return array + */ + public function getObjectKeys() + { + return $this->getKeys(); + } + + /** + * @param array $ordering + * @return ObjectCollectionInterface + * @phpstan-return C + */ + public function orderBy(array $ordering) + { + return $this->__call('orderBy', [$ordering]); + } + + /** + * @param string $method + * @param array $arguments + * @return array|mixed + */ + public function call($method, array $arguments = []) + { + return $this->__call('call', [$method, $arguments]); + } + + /** + * Group items in the collection by a field and return them as associated array. + * + * @param string $property + * @return array + */ + public function group($property) + { + return $this->__call('group', [$property]); + } + + /** + * Group items in the collection by a field and return them as associated array of collections. + * + * @param string $property + * @return ObjectCollectionInterface[] + * @phpstan-return C[] + */ + public function collectionGroup($property) + { + return $this->__call('collectionGroup', [$property]); + } + + /** + * @param Criteria $criteria + * @return ObjectCollectionInterface + * @phpstan-return C + */ + public function matching(Criteria $criteria) + { + $collection = $this->loadCollection($this->getEntries()); + + /** @phpstan-var C $matching */ + $matching = $collection->matching($criteria); + + return $matching; + } + + /** + * @param string $name + * @param array $arguments + * @return mixed + */ + #[\ReturnTypeWillChange] + abstract public function __call($name, $arguments); + + /** + * @return string + */ + protected function getTypePrefix() + { + return ''; + } +} diff --git a/system/src/Grav/Framework/Object/Property/ArrayPropertyTrait.php b/system/src/Grav/Framework/Object/Property/ArrayPropertyTrait.php new file mode 100644 index 0000000..a5e89e5 --- /dev/null +++ b/system/src/Grav/Framework/Object/Property/ArrayPropertyTrait.php @@ -0,0 +1,115 @@ +setElements($elements); + $this->setKey($key ?? ''); + } + + /** + * @param string $property Object property name. + * @return bool True if property has been defined (can be null). + */ + protected function doHasProperty($property) + { + return array_key_exists($property, $this->_elements); + } + + /** + * @param string $property Object property to be fetched. + * @param mixed $default Default value if property has not been set. + * @param bool $doCreate Set true to create variable. + * @return mixed Property value. + */ + protected function &doGetProperty($property, $default = null, $doCreate = false) + { + if (!array_key_exists($property, $this->_elements)) { + if ($doCreate) { + $this->_elements[$property] = null; + } else { + return $default; + } + } + + return $this->_elements[$property]; + } + + /** + * @param string $property Object property to be updated. + * @param mixed $value New value. + * @return void + */ + protected function doSetProperty($property, $value) + { + $this->_elements[$property] = $value; + } + + /** + * @param string $property Object property to be unset. + * @return void + */ + protected function doUnsetProperty($property) + { + unset($this->_elements[$property]); + } + + /** + * @param string $property + * @param mixed|null $default + * @return mixed|null + */ + protected function getElement($property, $default = null) + { + return array_key_exists($property, $this->_elements) ? $this->_elements[$property] : $default; + } + + /** + * @return array + */ + protected function getElements() + { + return array_filter($this->_elements, static function ($val) { + return $val !== null; + }); + } + + /** + * @param array $elements + * @return void + */ + protected function setElements(array $elements) + { + $this->_elements = $elements; + } + + abstract protected function setKey($key); +} diff --git a/system/src/Grav/Framework/Object/Property/LazyPropertyTrait.php b/system/src/Grav/Framework/Object/Property/LazyPropertyTrait.php new file mode 100644 index 0000000..a2ca5be --- /dev/null +++ b/system/src/Grav/Framework/Object/Property/LazyPropertyTrait.php @@ -0,0 +1,114 @@ +offsetLoad($offset, $value)` called first time object property gets accessed + * - `$this->offsetPrepare($offset, $value)` called on every object property set + * - `$this->offsetSerialize($offset, $value)` called when the raw or serialized object property value is needed + * + * @package Grav\Framework\Object\Property + */ +trait LazyPropertyTrait +{ + use ArrayPropertyTrait, ObjectPropertyTrait { + ObjectPropertyTrait::__construct insteadof ArrayPropertyTrait; + ArrayPropertyTrait::doHasProperty as hasArrayProperty; + ArrayPropertyTrait::doGetProperty as getArrayProperty; + ArrayPropertyTrait::doSetProperty as setArrayProperty; + ArrayPropertyTrait::doUnsetProperty as unsetArrayProperty; + ArrayPropertyTrait::getElement as getArrayElement; + ArrayPropertyTrait::getElements as getArrayElements; + ArrayPropertyTrait::setElements insteadof ObjectPropertyTrait; + ObjectPropertyTrait::doHasProperty as hasObjectProperty; + ObjectPropertyTrait::doGetProperty as getObjectProperty; + ObjectPropertyTrait::doSetProperty as setObjectProperty; + ObjectPropertyTrait::doUnsetProperty as unsetObjectProperty; + ObjectPropertyTrait::getElement as getObjectElement; + ObjectPropertyTrait::getElements as getObjectElements; + } + + /** + * @param string $property Object property name. + * @return bool True if property has been defined (can be null). + */ + protected function doHasProperty($property) + { + return $this->hasArrayProperty($property) || $this->hasObjectProperty($property); + } + + /** + * @param string $property Object property to be fetched. + * @param mixed $default Default value if property has not been set. + * @param bool $doCreate + * @return mixed Property value. + */ + protected function &doGetProperty($property, $default = null, $doCreate = false) + { + if ($this->hasObjectProperty($property)) { + return $this->getObjectProperty($property, $default, function ($default = null) use ($property) { + return $this->getArrayProperty($property, $default); + }); + } + + return $this->getArrayProperty($property, $default, $doCreate); + } + + /** + * @param string $property Object property to be updated. + * @param mixed $value New value. + * @return void + */ + protected function doSetProperty($property, $value) + { + if ($this->hasObjectProperty($property)) { + $this->setObjectProperty($property, $value); + } else { + $this->setArrayProperty($property, $value); + } + } + + /** + * @param string $property Object property to be unset. + * @return void + */ + protected function doUnsetProperty($property) + { + $this->hasObjectProperty($property) ? $this->unsetObjectProperty($property) : $this->unsetArrayProperty($property); + } + + /** + * @param string $property + * @param mixed|null $default + * @return mixed|null + */ + protected function getElement($property, $default = null) + { + if ($this->isPropertyLoaded($property)) { + return $this->getObjectElement($property, $default); + } + + return $this->getArrayElement($property, $default); + } + + /** + * @return array + */ + protected function getElements() + { + return $this->getObjectElements() + $this->getArrayElements(); + } +} diff --git a/system/src/Grav/Framework/Object/Property/MixedPropertyTrait.php b/system/src/Grav/Framework/Object/Property/MixedPropertyTrait.php new file mode 100644 index 0000000..4df00a1 --- /dev/null +++ b/system/src/Grav/Framework/Object/Property/MixedPropertyTrait.php @@ -0,0 +1,121 @@ +offsetLoad($offset, $value)` called first time object property gets accessed + * - `$this->offsetPrepare($offset, $value)` called on every object property set + * - `$this->offsetSerialize($offset, $value)` called when the raw or serialized object property value is needed + + * + * @package Grav\Framework\Object\Property + */ +trait MixedPropertyTrait +{ + use ArrayPropertyTrait, ObjectPropertyTrait { + ObjectPropertyTrait::__construct insteadof ArrayPropertyTrait; + ArrayPropertyTrait::doHasProperty as hasArrayProperty; + ArrayPropertyTrait::doGetProperty as getArrayProperty; + ArrayPropertyTrait::doSetProperty as setArrayProperty; + ArrayPropertyTrait::doUnsetProperty as unsetArrayProperty; + ArrayPropertyTrait::getElement as getArrayElement; + ArrayPropertyTrait::getElements as getArrayElements; + ArrayPropertyTrait::setElements as setArrayElements; + ObjectPropertyTrait::doHasProperty as hasObjectProperty; + ObjectPropertyTrait::doGetProperty as getObjectProperty; + ObjectPropertyTrait::doSetProperty as setObjectProperty; + ObjectPropertyTrait::doUnsetProperty as unsetObjectProperty; + ObjectPropertyTrait::getElement as getObjectElement; + ObjectPropertyTrait::getElements as getObjectElements; + ObjectPropertyTrait::setElements as setObjectElements; + } + + /** + * @param string $property Object property name. + * @return bool True if property has been defined (can be null). + */ + protected function doHasProperty($property) + { + return $this->hasArrayProperty($property) || $this->hasObjectProperty($property); + } + + /** + * @param string $property Object property to be fetched. + * @param mixed $default Default value if property has not been set. + * @param bool $doCreate + * @return mixed Property value. + */ + protected function &doGetProperty($property, $default = null, $doCreate = false) + { + if ($this->hasObjectProperty($property)) { + return $this->getObjectProperty($property); + } + + return $this->getArrayProperty($property, $default, $doCreate); + } + + /** + * @param string $property Object property to be updated. + * @param mixed $value New value. + * @return void + */ + protected function doSetProperty($property, $value) + { + $this->hasObjectProperty($property) + ? $this->setObjectProperty($property, $value) : $this->setArrayProperty($property, $value); + } + + /** + * @param string $property Object property to be unset. + * @return void + */ + protected function doUnsetProperty($property) + { + $this->hasObjectProperty($property) ? + $this->unsetObjectProperty($property) : $this->unsetArrayProperty($property); + } + + /** + * @param string $property + * @param mixed|null $default + * @return mixed|null + */ + protected function getElement($property, $default = null) + { + if ($this->hasObjectProperty($property)) { + return $this->getObjectElement($property, $default); + } + + return $this->getArrayElement($property, $default); + } + + /** + * @return array + */ + protected function getElements() + { + return $this->getObjectElements() + $this->getArrayElements(); + } + + /** + * @param array $elements + * @return void + */ + protected function setElements(array $elements) + { + $this->setObjectElements(array_intersect_key($elements, $this->_definedProperties)); + $this->setArrayElements(array_diff_key($elements, $this->_definedProperties)); + } +} diff --git a/system/src/Grav/Framework/Object/Property/ObjectPropertyTrait.php b/system/src/Grav/Framework/Object/Property/ObjectPropertyTrait.php new file mode 100644 index 0000000..3bc3bb6 --- /dev/null +++ b/system/src/Grav/Framework/Object/Property/ObjectPropertyTrait.php @@ -0,0 +1,213 @@ +offsetLoad($offset, $value)` called first time object property gets accessed + * - `$this->offsetPrepare($offset, $value)` called on every object property set + * - `$this->offsetSerialize($offset, $value)` called when the raw or serialized object property value is needed + * + * @package Grav\Framework\Object\Property + */ +trait ObjectPropertyTrait +{ + /** @var array */ + private $_definedProperties; + + /** + * @param array $elements + * @param string|null $key + * @throws InvalidArgumentException + */ + public function __construct(array $elements = [], $key = null) + { + $this->initObjectProperties(); + $this->setElements($elements); + $this->setKey($key ?? ''); + } + + /** + * @param string $property Object property name. + * @return bool True if property has been loaded. + */ + protected function isPropertyLoaded($property) + { + return !empty($this->_definedProperties[$property]); + } + + /** + * @param string $offset + * @param mixed $value + * @return mixed + */ + protected function offsetLoad($offset, $value) + { + $methodName = "offsetLoad_{$offset}"; + + return method_exists($this, $methodName)? $this->{$methodName}($value) : $value; + } + + /** + * @param string $offset + * @param mixed $value + * @return mixed + */ + protected function offsetPrepare($offset, $value) + { + $methodName = "offsetPrepare_{$offset}"; + + return method_exists($this, $methodName) ? $this->{$methodName}($value) : $value; + } + + /** + * @param string $offset + * @param mixed $value + * @return mixed + */ + protected function offsetSerialize($offset, $value) + { + $methodName = "offsetSerialize_{$offset}"; + + return method_exists($this, $methodName) ? $this->{$methodName}($value) : $value; + } + + /** + * @param string $property Object property name. + * @return bool True if property has been defined (can be null). + */ + protected function doHasProperty($property) + { + return array_key_exists($property, $this->_definedProperties); + } + + /** + * @param string $property Object property to be fetched. + * @param mixed $default Default value if property has not been set. + * @param callable|bool $doCreate Set true to create variable. + * @return mixed Property value. + */ + protected function &doGetProperty($property, $default = null, $doCreate = false) + { + if (!array_key_exists($property, $this->_definedProperties)) { + throw new InvalidArgumentException("Property '{$property}' does not exist in the object!"); + } + + if (empty($this->_definedProperties[$property])) { + if ($doCreate === true) { + $this->_definedProperties[$property] = true; + $this->{$property} = null; + } elseif (is_callable($doCreate)) { + $this->_definedProperties[$property] = true; + $this->{$property} = $this->offsetLoad($property, $doCreate()); + } else { + return $default; + } + } + + return $this->{$property}; + } + + /** + * @param string $property Object property to be updated. + * @param mixed $value New value. + * @return void + * @throws InvalidArgumentException + */ + protected function doSetProperty($property, $value) + { + if (!array_key_exists($property, $this->_definedProperties)) { + throw new InvalidArgumentException("Property '{$property}' does not exist in the object!"); + } + + $this->_definedProperties[$property] = true; + $this->{$property} = $this->offsetPrepare($property, $value); + } + + /** + * @param string $property Object property to be unset. + * @return void + */ + protected function doUnsetProperty($property) + { + if (!array_key_exists($property, $this->_definedProperties)) { + return; + } + + $this->_definedProperties[$property] = false; + $this->{$property} = null; + } + + /** + * @return void + */ + protected function initObjectProperties() + { + $this->_definedProperties = []; + foreach (get_object_vars($this) as $property => $value) { + if ($property[0] !== '_') { + $this->_definedProperties[$property] = ($value !== null); + } + } + } + + /** + * @param string $property + * @param mixed|null $default + * @return mixed|null + */ + protected function getElement($property, $default = null) + { + if (empty($this->_definedProperties[$property])) { + return $default; + } + + return $this->offsetSerialize($property, $this->{$property}); + } + + /** + * @return array + */ + protected function getElements() + { + $properties = array_intersect_key(get_object_vars($this), array_filter($this->_definedProperties)); + + $elements = []; + foreach ($properties as $offset => $value) { + $serialized = $this->offsetSerialize($offset, $value); + if ($serialized !== null) { + $elements[$offset] = $this->offsetSerialize($offset, $value); + } + } + + return $elements; + } + + /** + * @param array $elements + * @return void + */ + protected function setElements(array $elements) + { + foreach ($elements as $property => $value) { + $this->setProperty($property, $value); + } + } +} diff --git a/system/src/Grav/Framework/Object/PropertyObject.php b/system/src/Grav/Framework/Object/PropertyObject.php new file mode 100644 index 0000000..d7d1aba --- /dev/null +++ b/system/src/Grav/Framework/Object/PropertyObject.php @@ -0,0 +1,32 @@ + + */ +class PropertyObject implements NestedObjectInterface, ArrayAccess +{ + use ObjectTrait; + use ObjectPropertyTrait; + use NestedPropertyTrait; + use OverloadedPropertyTrait; + use NestedArrayAccessTrait; +} diff --git a/system/src/Grav/Framework/Pagination/AbstractPagination.php b/system/src/Grav/Framework/Pagination/AbstractPagination.php new file mode 100644 index 0000000..e170b4c --- /dev/null +++ b/system/src/Grav/Framework/Pagination/AbstractPagination.php @@ -0,0 +1,429 @@ + 'page', + 'limit' => 10, + 'display' => 5, + 'opening' => 0, + 'ending' => 0, + 'url' => null, + 'param' => null, + 'use_query_param' => false + ]; + /** @var array */ + private $items; + + /** + * @return bool + */ + public function isEnabled(): bool + { + return $this->count() > 1; + } + + /** + * @return array + */ + public function getOptions(): array + { + return $this->options; + } + + /** + * @return Route|null + */ + public function getRoute(): ?Route + { + return $this->route; + } + + /** + * @return int + */ + public function getTotalPages(): int + { + return $this->pages; + } + + /** + * @return int + */ + public function getPageNumber(): int + { + return $this->page ?? 1; + } + + /** + * @param int $count + * @return int|null + */ + public function getPrevNumber(int $count = 1): ?int + { + $page = $this->page - $count; + + return $page >= 1 ? $page : null; + } + + /** + * @param int $count + * @return int|null + */ + public function getNextNumber(int $count = 1): ?int + { + $page = $this->page + $count; + + return $page <= $this->pages ? $page : null; + } + + /** + * @param int $page + * @param string|null $label + * @return PaginationPage|null + */ + public function getPage(int $page, string $label = null): ?PaginationPage + { + if ($page < 1 || $page > $this->pages) { + return null; + } + + $start = ($page - 1) * $this->limit; + $type = $this->getOptions()['type']; + $param = $this->getOptions()['param']; + $useQuery = $this->getOptions()['use_query_param']; + if ($type === 'page') { + $param = $param ?? 'page'; + $offset = $page; + } else { + $param = $param ?? 'start'; + $offset = $start; + } + + if ($useQuery) { + $route = $this->route->withQueryParam($param, $offset); + } else { + $route = $this->route->withGravParam($param, $offset); + } + + return new PaginationPage( + [ + 'label' => $label ?? (string)$page, + 'number' => $page, + 'offset_start' => $start, + 'offset_end' => min($start + $this->limit, $this->total) - 1, + 'enabled' => $page !== $this->page || $this->viewAll, + 'active' => $page === $this->page, + 'route' => $route + ] + ); + } + + /** + * @param string|null $label + * @param int $count + * @return PaginationPage|null + */ + public function getFirstPage(string $label = null, int $count = 0): ?PaginationPage + { + return $this->getPage(1 + $count, $label ?? $this->getOptions()['label_first'] ?? null); + } + + /** + * @param string|null $label + * @param int $count + * @return PaginationPage|null + */ + public function getPrevPage(string $label = null, int $count = 1): ?PaginationPage + { + return $this->getPage($this->page - $count, $label ?? $this->getOptions()['label_prev'] ?? null); + } + + /** + * @param string|null $label + * @param int $count + * @return PaginationPage|null + */ + public function getNextPage(string $label = null, int $count = 1): ?PaginationPage + { + return $this->getPage($this->page + $count, $label ?? $this->getOptions()['label_next'] ?? null); + } + + /** + * @param string|null $label + * @param int $count + * @return PaginationPage|null + */ + public function getLastPage(string $label = null, int $count = 0): ?PaginationPage + { + return $this->getPage($this->pages - $count, $label ?? $this->getOptions()['label_last'] ?? null); + } + + /** + * @return int + */ + public function getStart(): int + { + return $this->start ?? 0; + } + + /** + * @return int + */ + public function getLimit(): int + { + return $this->limit; + } + + /** + * @return int + */ + public function getTotal(): int + { + return $this->total; + } + + /** + * @return int + */ + public function count(): int + { + $this->loadItems(); + + return count($this->items); + } + + /** + * @return ArrayIterator + * @phpstan-return ArrayIterator + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + $this->loadItems(); + + return new ArrayIterator($this->items); + } + + /** + * @return array + */ + public function getPages(): array + { + $this->loadItems(); + + return $this->items; + } + + /** + * @return void + */ + protected function loadItems() + { + $this->calculateRange(); + + // Make list like: 1 ... 4 5 6 ... 10 + $range = range($this->pagesStart, $this->pagesStop); + //$range[] = 1; + //$range[] = $this->pages; + natsort($range); + $range = array_unique($range); + + $this->items = []; + foreach ($range as $i) { + $this->items[$i] = $this->getPage($i); + } + } + + /** + * @param Route $route + * @return $this + */ + protected function setRoute(Route $route) + { + $this->route = $route; + + return $this; + } + + /** + * @param array|null $options + * @return $this + */ + protected function setOptions(array $options = null) + { + $this->options = $options ? array_merge($this->defaultOptions, $options) : $this->defaultOptions; + + return $this; + } + + /** + * @param int|null $page + * @return $this + */ + protected function setPage(int $page = null) + { + $this->page = (int)max($page, 1); + $this->start = null; + + return $this; + } + + /** + * @param int|null $start + * @return $this + */ + protected function setStart(int $start = null) + { + $this->start = (int)max($start, 0); + $this->page = null; + + return $this; + } + + /** + * @param int|null $limit + * @return $this + */ + protected function setLimit(int $limit = null) + { + $this->limit = (int)max($limit ?? $this->getOptions()['limit'], 0); + + // No limit, display all records in a single page. + $this->viewAll = !$limit; + + return $this; + } + + /** + * @param int $total + * @return $this + */ + protected function setTotal(int $total) + { + $this->total = (int)max($total, 0); + + return $this; + } + + /** + * @param Route $route + * @param int $total + * @param int|null $pos + * @param int|null $limit + * @param array|null $options + * @return void + */ + protected function initialize(Route $route, int $total, int $pos = null, int $limit = null, array $options = null) + { + $this->setRoute($route); + $this->setOptions($options); + $this->setTotal($total); + if ($this->getOptions()['type'] === 'start') { + $this->setStart($pos); + } else { + $this->setPage($pos); + } + $this->setLimit($limit); + $this->calculateLimits(); + } + + /** + * @return void + */ + protected function calculateLimits() + { + $limit = $this->limit; + $total = $this->total; + + if (!$limit || $limit > $total) { + // All records fit into a single page. + $this->start = 0; + $this->page = 1; + $this->pages = 1; + + return; + } + + if (null === $this->start) { + // If we are using page, convert it to start. + $this->start = (int)(($this->page - 1) * $limit); + } + + if ($this->start > $total - $limit) { + // If start is greater than total count (i.e. we are asked to display records that don't exist) + // then set start to display the last natural page of results. + $this->start = (int)max(0, (ceil($total / $limit) - 1) * $limit); + } + + // Set the total pages and current page values. + $this->page = (int)ceil(($this->start + 1) / $limit); + $this->pages = (int)ceil($total / $limit); + } + + /** + * @return void + */ + protected function calculateRange() + { + $options = $this->getOptions(); + $displayed = $options['display']; + $opening = $options['opening']; + $ending = $options['ending']; + + // Set the pagination iteration loop values. + $this->pagesStart = $this->page - (int)($displayed / 2); + if ($this->pagesStart < 1 + $opening) { + $this->pagesStart = 1 + $opening; + } + if ($this->pagesStart + $displayed - $opening > $this->pages) { + $this->pagesStop = $this->pages; + if ($this->pages < $displayed) { + $this->pagesStart = 1 + $opening; + } else { + $this->pagesStart = $this->pages - $displayed + 1 + $opening; + } + } else { + $this->pagesStop = (int)max(1, $this->pagesStart + $displayed - 1 - $ending); + } + } +} diff --git a/system/src/Grav/Framework/Pagination/AbstractPaginationPage.php b/system/src/Grav/Framework/Pagination/AbstractPaginationPage.php new file mode 100644 index 0000000..7ed55ae --- /dev/null +++ b/system/src/Grav/Framework/Pagination/AbstractPaginationPage.php @@ -0,0 +1,78 @@ +options['active'] ?? false; + } + + /** + * @return bool + */ + public function isEnabled(): bool + { + return $this->options['enabled'] ?? false; + } + + /** + * @return array + */ + public function getOptions(): array + { + return $this->options; + } + + /** + * @return int|null + */ + public function getNumber(): ?int + { + return $this->options['number'] ?? null; + } + + /** + * @return string + */ + public function getLabel(): string + { + return $this->options['label'] ?? (string)$this->getNumber(); + } + + /** + * @return string|null + */ + public function getUrl(): ?string + { + return $this->options['route'] ? (string)$this->options['route']->getUri() : null; + } + + /** + * @param array $options + */ + protected function setOptions(array $options): void + { + $this->options = $options; + } +} diff --git a/system/src/Grav/Framework/Pagination/Interfaces/PaginationInterface.php b/system/src/Grav/Framework/Pagination/Interfaces/PaginationInterface.php new file mode 100644 index 0000000..98b313d --- /dev/null +++ b/system/src/Grav/Framework/Pagination/Interfaces/PaginationInterface.php @@ -0,0 +1,104 @@ + + */ +interface PaginationInterface extends Countable, IteratorAggregate +{ + /** + * @return int + */ + public function getTotalPages(): int; + + /** + * @return int + */ + public function getPageNumber(): int; + + /** + * @param int $count + * @return int|null + */ + public function getPrevNumber(int $count = 1): ?int; + + /** + * @param int $count + * @return int|null + */ + public function getNextNumber(int $count = 1): ?int; + + /** + * @return int + */ + public function getStart(): int; + + /** + * @return int + */ + public function getLimit(): int; + + /** + * @return int + */ + public function getTotal(): int; + + /** + * @return int + */ + public function count(): int; + + /** + * @return array + */ + public function getOptions(): array; + + /** + * @param int $page + * @param string|null $label + * @return PaginationPage|null + */ + public function getPage(int $page, string $label = null): ?PaginationPage; + + /** + * @param string|null $label + * @param int $count + * @return PaginationPage|null + */ + public function getFirstPage(string $label = null, int $count = 0): ?PaginationPage; + + /** + * @param string|null $label + * @param int $count + * @return PaginationPage|null + */ + public function getPrevPage(string $label = null, int $count = 1): ?PaginationPage; + + /** + * @param string|null $label + * @param int $count + * @return PaginationPage|null + */ + public function getNextPage(string $label = null, int $count = 1): ?PaginationPage; + + /** + * @param string|null $label + * @param int $count + * @return PaginationPage|null + */ + public function getLastPage(string $label = null, int $count = 0): ?PaginationPage; +} diff --git a/system/src/Grav/Framework/Pagination/Interfaces/PaginationPageInterface.php b/system/src/Grav/Framework/Pagination/Interfaces/PaginationPageInterface.php new file mode 100644 index 0000000..5158e94 --- /dev/null +++ b/system/src/Grav/Framework/Pagination/Interfaces/PaginationPageInterface.php @@ -0,0 +1,47 @@ +initialize($route, $total, $pos, $limit, $options); + } +} diff --git a/system/src/Grav/Framework/Pagination/PaginationPage.php b/system/src/Grav/Framework/Pagination/PaginationPage.php new file mode 100644 index 0000000..73249ed --- /dev/null +++ b/system/src/Grav/Framework/Pagination/PaginationPage.php @@ -0,0 +1,26 @@ +setOptions($options); + } +} diff --git a/system/src/Grav/Framework/Psr7/AbstractUri.php b/system/src/Grav/Framework/Psr7/AbstractUri.php new file mode 100644 index 0000000..271de35 --- /dev/null +++ b/system/src/Grav/Framework/Psr7/AbstractUri.php @@ -0,0 +1,412 @@ + 80, + 'https' => 443 + ]; + + /** @var string Uri scheme. */ + private $scheme = ''; + /** @var string Uri user. */ + private $user = ''; + /** @var string Uri password. */ + private $password = ''; + /** @var string Uri host. */ + private $host = ''; + /** @var int|null Uri port. */ + private $port; + /** @var string Uri path. */ + private $path = ''; + /** @var string Uri query string (without ?). */ + private $query = ''; + /** @var string Uri fragment (without #). */ + private $fragment = ''; + + /** + * Please define constructor which calls $this->init(). + */ + abstract public function __construct(); + + /** + * @inheritdoc + */ + public function getScheme() + { + return $this->scheme; + } + + /** + * @inheritdoc + */ + public function getAuthority() + { + $authority = $this->host; + + $userInfo = $this->getUserInfo(); + if ($userInfo !== '') { + $authority = $userInfo . '@' . $authority; + } + + if ($this->port !== null) { + $authority .= ':' . $this->port; + } + + return $authority; + } + + /** + * @inheritdoc + */ + public function getUserInfo() + { + $userInfo = $this->user; + + if ($this->password !== '') { + $userInfo .= ':' . $this->password; + } + + return $userInfo; + } + + /** + * @inheritdoc + */ + public function getHost() + { + return $this->host; + } + + /** + * @inheritdoc + */ + public function getPort() + { + return $this->port; + } + + /** + * @inheritdoc + */ + public function getPath() + { + return $this->path; + } + + /** + * @inheritdoc + */ + public function getQuery() + { + return $this->query; + } + + /** + * @inheritdoc + */ + public function getFragment() + { + return $this->fragment; + } + + /** + * @inheritdoc + */ + public function withScheme($scheme) + { + $scheme = UriPartsFilter::filterScheme($scheme); + + if ($this->scheme === $scheme) { + return $this; + } + + $new = clone $this; + $new->scheme = $scheme; + $new->unsetDefaultPort(); + $new->validate(); + + return $new; + } + + /** + * @inheritdoc + * @throws InvalidArgumentException + */ + public function withUserInfo($user, $password = null) + { + $user = UriPartsFilter::filterUserInfo($user); + $password = UriPartsFilter::filterUserInfo($password ?? ''); + + if ($this->user === $user && $this->password === $password) { + return $this; + } + + $new = clone $this; + $new->user = $user; + $new->password = $user !== '' ? $password : ''; + $new->validate(); + + return $new; + } + + /** + * @inheritdoc + */ + public function withHost($host) + { + $host = UriPartsFilter::filterHost($host); + + if ($this->host === $host) { + return $this; + } + + $new = clone $this; + $new->host = $host; + $new->validate(); + + return $new; + } + + /** + * @inheritdoc + */ + public function withPort($port) + { + $port = UriPartsFilter::filterPort($port); + + if ($this->port === $port) { + return $this; + } + + $new = clone $this; + $new->port = $port; + $new->unsetDefaultPort(); + $new->validate(); + + return $new; + } + + /** + * @inheritdoc + */ + public function withPath($path) + { + $path = UriPartsFilter::filterPath($path); + + if ($this->path === $path) { + return $this; + } + + $new = clone $this; + $new->path = $path; + $new->validate(); + + return $new; + } + + /** + * @inheritdoc + */ + public function withQuery($query) + { + $query = UriPartsFilter::filterQueryOrFragment($query); + + if ($this->query === $query) { + return $this; + } + + $new = clone $this; + $new->query = $query; + + return $new; + } + + /** + * @inheritdoc + * @throws InvalidArgumentException + */ + public function withFragment($fragment) + { + $fragment = UriPartsFilter::filterQueryOrFragment($fragment); + + if ($this->fragment === $fragment) { + return $this; + } + + $new = clone $this; + $new->fragment = $fragment; + + return $new; + } + + /** + * @return string + */ + #[\ReturnTypeWillChange] + public function __toString() + { + return $this->getUrl(); + } + + /** + * @return array + */ + protected function getParts() + { + return [ + 'scheme' => $this->scheme, + 'host' => $this->host, + 'port' => $this->port, + 'user' => $this->user, + 'pass' => $this->password, + 'path' => $this->path, + 'query' => $this->query, + 'fragment' => $this->fragment + ]; + } + + /** + * Return the fully qualified base URL ( like http://getgrav.org ). + * + * Note that this method never includes a trailing / + * + * @return string + */ + protected function getBaseUrl() + { + $uri = ''; + + $scheme = $this->getScheme(); + if ($scheme !== '') { + $uri .= $scheme . ':'; + } + + $authority = $this->getAuthority(); + if ($authority !== '' || $scheme === 'file') { + $uri .= '//' . $authority; + } + + return $uri; + } + + /** + * @return string + */ + protected function getUrl() + { + $uri = $this->getBaseUrl() . $this->getPath(); + + $query = $this->getQuery(); + if ($query !== '') { + $uri .= '?' . $query; + } + + $fragment = $this->getFragment(); + if ($fragment !== '') { + $uri .= '#' . $fragment; + } + + return $uri; + } + + /** + * @return string + */ + protected function getUser() + { + return $this->user; + } + + /** + * @return string + */ + protected function getPassword() + { + return $this->password; + } + + /** + * @param array $parts + * @return void + * @throws InvalidArgumentException + */ + protected function initParts(array $parts) + { + $this->scheme = isset($parts['scheme']) ? UriPartsFilter::filterScheme($parts['scheme']) : ''; + $this->user = isset($parts['user']) ? UriPartsFilter::filterUserInfo($parts['user']) : ''; + $this->password = isset($parts['pass']) ? UriPartsFilter::filterUserInfo($parts['pass']) : ''; + $this->host = isset($parts['host']) ? UriPartsFilter::filterHost($parts['host']) : ''; + $this->port = isset($parts['port']) ? UriPartsFilter::filterPort((int)$parts['port']) : null; + $this->path = isset($parts['path']) ? UriPartsFilter::filterPath($parts['path']) : ''; + $this->query = isset($parts['query']) ? UriPartsFilter::filterQueryOrFragment($parts['query']) : ''; + $this->fragment = isset($parts['fragment']) ? UriPartsFilter::filterQueryOrFragment($parts['fragment']) : ''; + + $this->unsetDefaultPort(); + $this->validate(); + } + + /** + * @return void + * @throws InvalidArgumentException + */ + private function validate() + { + if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) { + throw new InvalidArgumentException('Uri with a scheme must have a host'); + } + + if ($this->getAuthority() === '') { + if (0 === strpos($this->path, '//')) { + throw new InvalidArgumentException('The path of a URI without an authority must not start with two slashes \'//\''); + } + if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) { + throw new InvalidArgumentException('A relative URI must not have a path beginning with a segment containing a colon'); + } + } elseif (isset($this->path[0]) && $this->path[0] !== '/') { + throw new InvalidArgumentException('The path of a URI with an authority must start with a slash \'/\' or be empty'); + } + } + + /** + * @return bool + */ + protected function isDefaultPort() + { + $scheme = $this->scheme; + $port = $this->port; + + return $this->port === null + || (isset(static::$defaultPorts[$scheme]) && $port === static::$defaultPorts[$scheme]); + } + + /** + * @return void + */ + private function unsetDefaultPort() + { + if ($this->isDefaultPort()) { + $this->port = null; + } + } +} diff --git a/system/src/Grav/Framework/Psr7/Request.php b/system/src/Grav/Framework/Psr7/Request.php new file mode 100644 index 0000000..4339ee8 --- /dev/null +++ b/system/src/Grav/Framework/Psr7/Request.php @@ -0,0 +1,34 @@ +message = new \Nyholm\Psr7\Request($method, $uri, $headers, $body, $version); + } +} diff --git a/system/src/Grav/Framework/Psr7/Response.php b/system/src/Grav/Framework/Psr7/Response.php new file mode 100644 index 0000000..508f9be --- /dev/null +++ b/system/src/Grav/Framework/Psr7/Response.php @@ -0,0 +1,265 @@ +message = new \Nyholm\Psr7\Response($status, $headers, $body, $version, $reason); + } + + /** + * Json. + * + * Note: This method is not part of the PSR-7 standard. + * + * This method prepares the response object to return an HTTP Json + * response to the client. + * + * @param mixed $data The data + * @param int|null $status The HTTP status code. + * @param int $options Json encoding options + * @param int $depth Json encoding max depth + * @return static + * @phpstan-param positive-int $depth + */ + public function withJson($data, int $status = null, int $options = 0, int $depth = 512): ResponseInterface + { + $json = (string) json_encode($data, $options, $depth); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new RuntimeException(json_last_error_msg(), json_last_error()); + } + + $response = $this->getResponse() + ->withHeader('Content-Type', 'application/json;charset=utf-8') + ->withBody(new Stream($json)); + + if ($status !== null) { + $response = $response->withStatus($status); + } + + $new = clone $this; + $new->message = $response; + + return $new; + } + + /** + * Redirect. + * + * Note: This method is not part of the PSR-7 standard. + * + * This method prepares the response object to return an HTTP Redirect + * response to the client. + * + * @param string $url The redirect destination. + * @param int|null $status The redirect HTTP status code. + * @return static + */ + public function withRedirect(string $url, $status = null): ResponseInterface + { + $response = $this->getResponse()->withHeader('Location', $url); + + if ($status === null) { + $status = 302; + } + + $new = clone $this; + $new->message = $response->withStatus($status); + + return $new; + } + + /** + * Is this response empty? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isEmpty(): bool + { + return in_array($this->getResponse()->getStatusCode(), [204, 205, 304], true); + } + + + /** + * Is this response OK? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isOk(): bool + { + return $this->getResponse()->getStatusCode() === 200; + } + + /** + * Is this response a redirect? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isRedirect(): bool + { + return in_array($this->getResponse()->getStatusCode(), [301, 302, 303, 307, 308], true); + } + + /** + * Is this response forbidden? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + * @api + */ + public function isForbidden(): bool + { + return $this->getResponse()->getStatusCode() === 403; + } + + /** + * Is this response not Found? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isNotFound(): bool + { + return $this->getResponse()->getStatusCode() === 404; + } + + /** + * Is this response informational? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isInformational(): bool + { + $response = $this->getResponse(); + + return $response->getStatusCode() >= 100 && $response->getStatusCode() < 200; + } + + /** + * Is this response successful? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isSuccessful(): bool + { + $response = $this->getResponse(); + + return $response->getStatusCode() >= 200 && $response->getStatusCode() < 300; + } + + /** + * Is this response a redirection? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isRedirection(): bool + { + $response = $this->getResponse(); + + return $response->getStatusCode() >= 300 && $response->getStatusCode() < 400; + } + + /** + * Is this response a client error? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isClientError(): bool + { + $response = $this->getResponse(); + + return $response->getStatusCode() >= 400 && $response->getStatusCode() < 500; + } + + /** + * Is this response a server error? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isServerError(): bool + { + $response = $this->getResponse(); + + return $response->getStatusCode() >= 500 && $response->getStatusCode() < 600; + } + + /** + * Convert response to string. + * + * Note: This method is not part of the PSR-7 standard. + * + * @return string + */ + public function __toString(): string + { + $response = $this->getResponse(); + $output = sprintf( + 'HTTP/%s %s %s%s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase(), + self::EOL + ); + + foreach ($response->getHeaders() as $name => $values) { + $output .= sprintf('%s: %s', $name, $response->getHeaderLine($name)) . self::EOL; + } + + $output .= self::EOL; + $output .= $response->getBody(); + + return $output; + } +} diff --git a/system/src/Grav/Framework/Psr7/ServerRequest.php b/system/src/Grav/Framework/Psr7/ServerRequest.php new file mode 100644 index 0000000..858151a --- /dev/null +++ b/system/src/Grav/Framework/Psr7/ServerRequest.php @@ -0,0 +1,364 @@ +message = new \Nyholm\Psr7\ServerRequest($method, $uri, $headers, $body, $version, $serverParams); + } + + /** + * Get serverRequest content character set, if known. + * + * Note: This method is not part of the PSR-7 standard. + * + * @return string|null + */ + public function getContentCharset(): ?string + { + $mediaTypeParams = $this->getMediaTypeParams(); + + if (isset($mediaTypeParams['charset'])) { + return $mediaTypeParams['charset']; + } + + return null; + } + + /** + * Get serverRequest content type. + * + * Note: This method is not part of the PSR-7 standard. + * + * @return string|null The serverRequest content type, if known + */ + public function getContentType(): ?string + { + $result = $this->getRequest()->getHeader('Content-Type'); + + return $result ? $result[0] : null; + } + + /** + * Get serverRequest content length, if known. + * + * Note: This method is not part of the PSR-7 standard. + * + * @return int|null + */ + public function getContentLength(): ?int + { + $result = $this->getRequest()->getHeader('Content-Length'); + + return $result ? (int) $result[0] : null; + } + + /** + * Fetch cookie value from cookies sent by the client to the server. + * + * Note: This method is not part of the PSR-7 standard. + * + * @param string $key The attribute name. + * @param mixed $default Default value to return if the attribute does not exist. + * + * @return mixed + */ + public function getCookieParam($key, $default = null) + { + $cookies = $this->getRequest()->getCookieParams(); + + return $cookies[$key] ?? $default; + } + + /** + * Get serverRequest media type, if known. + * + * Note: This method is not part of the PSR-7 standard. + * + * @return string|null The serverRequest media type, minus content-type params + */ + public function getMediaType(): ?string + { + $contentType = $this->getContentType(); + + if ($contentType) { + $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType); + if ($contentTypeParts === false) { + return null; + } + return strtolower($contentTypeParts[0]); + } + + return null; + } + + /** + * Get serverRequest media type params, if known. + * + * Note: This method is not part of the PSR-7 standard. + * + * @return mixed[] + */ + public function getMediaTypeParams(): array + { + $contentType = $this->getContentType(); + $contentTypeParams = []; + + if ($contentType) { + $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType); + if ($contentTypeParts !== false) { + $contentTypePartsLength = count($contentTypeParts); + for ($i = 1; $i < $contentTypePartsLength; $i++) { + $paramParts = explode('=', $contentTypeParts[$i]); + $contentTypeParams[strtolower($paramParts[0])] = $paramParts[1]; + } + } + } + + return $contentTypeParams; + } + + /** + * Fetch serverRequest parameter value from body or query string (in that order). + * + * Note: This method is not part of the PSR-7 standard. + * + * @param string $key The parameter key. + * @param string|null $default The default value. + * + * @return mixed The parameter value. + */ + public function getParam($key, $default = null) + { + $postParams = $this->getParsedBody(); + $getParams = $this->getQueryParams(); + $result = $default; + + if (is_array($postParams) && isset($postParams[$key])) { + $result = $postParams[$key]; + } elseif (is_object($postParams) && property_exists($postParams, $key)) { + $result = $postParams->$key; + } elseif (isset($getParams[$key])) { + $result = $getParams[$key]; + } + + return $result; + } + + /** + * Fetch associative array of body and query string parameters. + * + * Note: This method is not part of the PSR-7 standard. + * + * @return mixed[] + */ + public function getParams(): array + { + $params = $this->getQueryParams(); + $postParams = $this->getParsedBody(); + + if ($postParams) { + $params = array_merge($params, (array)$postParams); + } + + return $params; + } + + /** + * Fetch parameter value from serverRequest body. + * + * Note: This method is not part of the PSR-7 standard. + * + * @param string $key + * @param mixed $default + * + * @return mixed + */ + public function getParsedBodyParam($key, $default = null) + { + $postParams = $this->getParsedBody(); + $result = $default; + + if (is_array($postParams) && isset($postParams[$key])) { + $result = $postParams[$key]; + } elseif (is_object($postParams) && property_exists($postParams, $key)) { + $result = $postParams->{$key}; + } + + return $result; + } + + /** + * Fetch parameter value from query string. + * + * Note: This method is not part of the PSR-7 standard. + * + * @param string $key + * @param mixed $default + * + * @return mixed + */ + public function getQueryParam($key, $default = null) + { + $getParams = $this->getQueryParams(); + + return $getParams[$key] ?? $default; + } + + /** + * Retrieve a server parameter. + * + * Note: This method is not part of the PSR-7 standard. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function getServerParam($key, $default = null) + { + $serverParams = $this->getRequest()->getServerParams(); + + return $serverParams[$key] ?? $default; + } + + /** + * Does this serverRequest use a given method? + * + * Note: This method is not part of the PSR-7 standard. + * + * @param string $method HTTP method + * @return bool + */ + public function isMethod($method): bool + { + return $this->getRequest()->getMethod() === $method; + } + + /** + * Is this a DELETE serverRequest? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isDelete(): bool + { + return $this->isMethod('DELETE'); + } + + /** + * Is this a GET serverRequest? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isGet(): bool + { + return $this->isMethod('GET'); + } + + /** + * Is this a HEAD serverRequest? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isHead(): bool + { + return $this->isMethod('HEAD'); + } + + /** + * Is this a OPTIONS serverRequest? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isOptions(): bool + { + return $this->isMethod('OPTIONS'); + } + + /** + * Is this a PATCH serverRequest? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isPatch(): bool + { + return $this->isMethod('PATCH'); + } + + /** + * Is this a POST serverRequest? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isPost(): bool + { + return $this->isMethod('POST'); + } + + /** + * Is this a PUT serverRequest? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isPut(): bool + { + return $this->isMethod('PUT'); + } + + /** + * Is this an XHR serverRequest? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isXhr(): bool + { + return $this->getRequest()->getHeaderLine('X-Requested-With') === 'XMLHttpRequest'; + } +} diff --git a/system/src/Grav/Framework/Psr7/Stream.php b/system/src/Grav/Framework/Psr7/Stream.php new file mode 100644 index 0000000..03f8536 --- /dev/null +++ b/system/src/Grav/Framework/Psr7/Stream.php @@ -0,0 +1,43 @@ +stream = \Nyholm\Psr7\Stream::create($body); + } +} diff --git a/system/src/Grav/Framework/Psr7/Traits/MessageDecoratorTrait.php b/system/src/Grav/Framework/Psr7/Traits/MessageDecoratorTrait.php new file mode 100644 index 0000000..9e5201c --- /dev/null +++ b/system/src/Grav/Framework/Psr7/Traits/MessageDecoratorTrait.php @@ -0,0 +1,140 @@ + + */ +trait MessageDecoratorTrait +{ + /** @var MessageInterface */ + private $message; + + /** + * Returns the decorated message. + * + * Since the underlying Message is immutable as well + * exposing it is not an issue, because it's state cannot be altered + * + * @return MessageInterface + */ + public function getMessage(): MessageInterface + { + return $this->message; + } + + /** + * {@inheritdoc} + */ + public function getProtocolVersion(): string + { + return $this->message->getProtocolVersion(); + } + + /** + * {@inheritdoc} + */ + public function withProtocolVersion($version): self + { + $new = clone $this; + $new->message = $this->message->withProtocolVersion($version); + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getHeaders(): array + { + return $this->message->getHeaders(); + } + + /** + * {@inheritdoc} + */ + public function hasHeader($header): bool + { + return $this->message->hasHeader($header); + } + + /** + * {@inheritdoc} + */ + public function getHeader($header): array + { + return $this->message->getHeader($header); + } + + /** + * {@inheritdoc} + */ + public function getHeaderLine($header): string + { + return $this->message->getHeaderLine($header); + } + + /** + * {@inheritdoc} + */ + public function getBody(): StreamInterface + { + return $this->message->getBody(); + } + + /** + * {@inheritdoc} + */ + public function withHeader($header, $value): self + { + $new = clone $this; + $new->message = $this->message->withHeader($header, $value); + + return $new; + } + + /** + * {@inheritdoc} + */ + public function withAddedHeader($header, $value): self + { + $new = clone $this; + $new->message = $this->message->withAddedHeader($header, $value); + + return $new; + } + + /** + * {@inheritdoc} + */ + public function withoutHeader($header): self + { + $new = clone $this; + $new->message = $this->message->withoutHeader($header); + + return $new; + } + + /** + * {@inheritdoc} + */ + public function withBody(StreamInterface $body): self + { + $new = clone $this; + $new->message = $this->message->withBody($body); + + return $new; + } +} diff --git a/system/src/Grav/Framework/Psr7/Traits/RequestDecoratorTrait.php b/system/src/Grav/Framework/Psr7/Traits/RequestDecoratorTrait.php new file mode 100644 index 0000000..a24a13f --- /dev/null +++ b/system/src/Grav/Framework/Psr7/Traits/RequestDecoratorTrait.php @@ -0,0 +1,112 @@ + + */ +trait RequestDecoratorTrait +{ + use MessageDecoratorTrait { + getMessage as private; + } + + /** + * Returns the decorated request. + * + * Since the underlying Request is immutable as well + * exposing it is not an issue, because it's state cannot be altered + * + * @return RequestInterface + */ + public function getRequest(): RequestInterface + { + /** @var RequestInterface $message */ + $message = $this->getMessage(); + + return $message; + } + + /** + * Exchanges the underlying request with another. + * + * @param RequestInterface $request + * @return self + */ + public function withRequest(RequestInterface $request): self + { + $new = clone $this; + $new->message = $request; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getRequestTarget(): string + { + return $this->getRequest()->getRequestTarget(); + } + + /** + * {@inheritdoc} + */ + public function withRequestTarget($requestTarget): self + { + $new = clone $this; + $new->message = $this->getRequest()->withRequestTarget($requestTarget); + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getMethod(): string + { + return $this->getRequest()->getMethod(); + } + + /** + * {@inheritdoc} + */ + public function withMethod($method): self + { + $new = clone $this; + $new->message = $this->getRequest()->withMethod($method); + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getUri(): UriInterface + { + return $this->getRequest()->getUri(); + } + + /** + * {@inheritdoc} + */ + public function withUri(UriInterface $uri, $preserveHost = false): self + { + $new = clone $this; + $new->message = $this->getRequest()->withUri($uri, $preserveHost); + + return $new; + } +} diff --git a/system/src/Grav/Framework/Psr7/Traits/ResponseDecoratorTrait.php b/system/src/Grav/Framework/Psr7/Traits/ResponseDecoratorTrait.php new file mode 100644 index 0000000..1bf29a6 --- /dev/null +++ b/system/src/Grav/Framework/Psr7/Traits/ResponseDecoratorTrait.php @@ -0,0 +1,82 @@ + + */ +trait ResponseDecoratorTrait +{ + use MessageDecoratorTrait { + getMessage as private; + } + + /** + * Returns the decorated response. + * + * Since the underlying Response is immutable as well + * exposing it is not an issue, because it's state cannot be altered + * + * @return ResponseInterface + */ + public function getResponse(): ResponseInterface + { + /** @var ResponseInterface $message */ + $message = $this->getMessage(); + + return $message; + } + + /** + * Exchanges the underlying response with another. + * + * @param ResponseInterface $response + * + * @return self + */ + public function withResponse(ResponseInterface $response): self + { + $new = clone $this; + $new->message = $response; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getStatusCode(): int + { + return $this->getResponse()->getStatusCode(); + } + + /** + * {@inheritdoc} + */ + public function withStatus($code, $reasonPhrase = ''): self + { + $new = clone $this; + $new->message = $this->getResponse()->withStatus($code, $reasonPhrase); + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getReasonPhrase(): string + { + return $this->getResponse()->getReasonPhrase(); + } +} diff --git a/system/src/Grav/Framework/Psr7/Traits/ServerRequestDecoratorTrait.php b/system/src/Grav/Framework/Psr7/Traits/ServerRequestDecoratorTrait.php new file mode 100644 index 0000000..4a4bd7f --- /dev/null +++ b/system/src/Grav/Framework/Psr7/Traits/ServerRequestDecoratorTrait.php @@ -0,0 +1,176 @@ +getMessage(); + + return $message; + } + + /** + * @inheritdoc + */ + public function getAttribute($name, $default = null) + { + return $this->getRequest()->getAttribute($name, $default); + } + + /** + * @inheritdoc + */ + public function getAttributes() + { + return $this->getRequest()->getAttributes(); + } + + + /** + * @inheritdoc + */ + public function getCookieParams() + { + return $this->getRequest()->getCookieParams(); + } + + /** + * @inheritdoc + */ + public function getParsedBody() + { + return $this->getRequest()->getParsedBody(); + } + + /** + * @inheritdoc + */ + public function getQueryParams() + { + return $this->getRequest()->getQueryParams(); + } + + /** + * @inheritdoc + */ + public function getServerParams() + { + return $this->getRequest()->getServerParams(); + } + + /** + * @inheritdoc + */ + public function getUploadedFiles() + { + return $this->getRequest()->getUploadedFiles(); + } + + /** + * @inheritdoc + */ + public function withAttribute($name, $value) + { + $new = clone $this; + $new->message = $this->getRequest()->withAttribute($name, $value); + + return $new; + } + + /** + * @param array $attributes + * @return ServerRequestInterface + */ + public function withAttributes(array $attributes) + { + $new = clone $this; + foreach ($attributes as $attribute => $value) { + $new->message = $new->withAttribute($attribute, $value); + } + + return $new; + } + + /** + * @inheritdoc + */ + public function withoutAttribute($name) + { + $new = clone $this; + $new->message = $this->getRequest()->withoutAttribute($name); + + return $new; + } + + /** + * @inheritdoc + */ + public function withCookieParams(array $cookies) + { + $new = clone $this; + $new->message = $this->getRequest()->withCookieParams($cookies); + + return $new; + } + + /** + * @inheritdoc + */ + public function withParsedBody($data) + { + $new = clone $this; + $new->message = $this->getRequest()->withParsedBody($data); + + return $new; + } + + /** + * @inheritdoc + */ + public function withQueryParams(array $query) + { + $new = clone $this; + $new->message = $this->getRequest()->withQueryParams($query); + + return $new; + } + + /** + * @inheritdoc + */ + public function withUploadedFiles(array $uploadedFiles) + { + $new = clone $this; + $new->message = $this->getRequest()->withUploadedFiles($uploadedFiles); + + return $new; + } +} diff --git a/system/src/Grav/Framework/Psr7/Traits/StreamDecoratorTrait.php b/system/src/Grav/Framework/Psr7/Traits/StreamDecoratorTrait.php new file mode 100644 index 0000000..a1c820e --- /dev/null +++ b/system/src/Grav/Framework/Psr7/Traits/StreamDecoratorTrait.php @@ -0,0 +1,153 @@ +stream->__toString(); + } + + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function __destruct() + { + $this->stream->close(); + } + + /** + * {@inheritdoc} + */ + public function close(): void + { + $this->stream->close(); + } + + /** + * {@inheritdoc} + */ + public function detach() + { + return $this->stream->detach(); + } + + /** + * {@inheritdoc} + */ + public function getSize(): ?int + { + return $this->stream->getSize(); + } + + /** + * {@inheritdoc} + */ + public function tell(): int + { + return $this->stream->tell(); + } + + /** + * {@inheritdoc} + */ + public function eof(): bool + { + return $this->stream->eof(); + } + + /** + * {@inheritdoc} + */ + public function isSeekable(): bool + { + return $this->stream->isSeekable(); + } + + /** + * {@inheritdoc} + */ + public function seek($offset, $whence = \SEEK_SET): void + { + $this->stream->seek($offset, $whence); + } + + /** + * {@inheritdoc} + */ + public function rewind(): void + { + $this->stream->rewind(); + } + + /** + * {@inheritdoc} + */ + public function isWritable(): bool + { + return $this->stream->isWritable(); + } + + /** + * {@inheritdoc} + */ + public function write($string): int + { + return $this->stream->write($string); + } + + /** + * {@inheritdoc} + */ + public function isReadable(): bool + { + return $this->stream->isReadable(); + } + + /** + * {@inheritdoc} + */ + public function read($length): string + { + return $this->stream->read($length); + } + + /** + * {@inheritdoc} + */ + public function getContents(): string + { + return $this->stream->getContents(); + } + + /** + * {@inheritdoc} + */ + public function getMetadata($key = null) + { + return $this->stream->getMetadata($key); + } +} diff --git a/system/src/Grav/Framework/Psr7/Traits/UploadedFileDecoratorTrait.php b/system/src/Grav/Framework/Psr7/Traits/UploadedFileDecoratorTrait.php new file mode 100644 index 0000000..e0a3e44 --- /dev/null +++ b/system/src/Grav/Framework/Psr7/Traits/UploadedFileDecoratorTrait.php @@ -0,0 +1,73 @@ +uploadedFile->getStream(); + } + + /** + * @param string $targetPath + */ + public function moveTo($targetPath): void + { + $this->uploadedFile->moveTo($targetPath); + } + + /** + * @return int|null + */ + public function getSize(): ?int + { + return $this->uploadedFile->getSize(); + } + + /** + * @return int + */ + public function getError(): int + { + return $this->uploadedFile->getError(); + } + + /** + * @return string|null + */ + public function getClientFilename(): ?string + { + return $this->uploadedFile->getClientFilename(); + } + + /** + * @return string|null + */ + public function getClientMediaType(): ?string + { + return $this->uploadedFile->getClientMediaType(); + } +} diff --git a/system/src/Grav/Framework/Psr7/Traits/UriDecorationTrait.php b/system/src/Grav/Framework/Psr7/Traits/UriDecorationTrait.php new file mode 100644 index 0000000..f697812 --- /dev/null +++ b/system/src/Grav/Framework/Psr7/Traits/UriDecorationTrait.php @@ -0,0 +1,188 @@ +uri->__toString(); + } + + /** + * @return string + */ + public function getScheme(): string + { + return $this->uri->getScheme(); + } + + /** + * @return string + */ + public function getAuthority(): string + { + return $this->uri->getAuthority(); + } + + /** + * @return string + */ + public function getUserInfo(): string + { + return $this->uri->getUserInfo(); + } + + /** + * @return string + */ + public function getHost(): string + { + return $this->uri->getHost(); + } + + /** + * @return int|null + */ + public function getPort(): ?int + { + return $this->uri->getPort(); + } + + /** + * @return string + */ + public function getPath(): string + { + return $this->uri->getPath(); + } + + /** + * @return string + */ + public function getQuery(): string + { + return $this->uri->getQuery(); + } + + /** + * @return string + */ + public function getFragment(): string + { + return $this->uri->getFragment(); + } + + /** + * @param string $scheme + * @return UriInterface + */ + public function withScheme($scheme): UriInterface + { + $new = clone $this; + $new->uri = $this->uri->withScheme($scheme); + + /** @var UriInterface $new */ + return $new; + } + + /** + * @param string $user + * @param string|null $password + * @return UriInterface + */ + public function withUserInfo($user, $password = null): UriInterface + { + $new = clone $this; + $new->uri = $this->uri->withUserInfo($user, $password); + + /** @var UriInterface $new */ + return $new; + } + + /** + * @param string $host + * @return UriInterface + */ + public function withHost($host): UriInterface + { + $new = clone $this; + $new->uri = $this->uri->withHost($host); + + /** @var UriInterface $new */ + return $new; + } + + /** + * @param int|null $port + * @return UriInterface + */ + public function withPort($port): UriInterface + { + $new = clone $this; + $new->uri = $this->uri->withPort($port); + + /** @var UriInterface $new */ + return $new; + } + + /** + * @param string $path + * @return UriInterface + */ + public function withPath($path): UriInterface + { + $new = clone $this; + $new->uri = $this->uri->withPath($path); + + /** @var UriInterface $new */ + return $new; + } + + /** + * @param string $query + * @return UriInterface + */ + public function withQuery($query): UriInterface + { + $new = clone $this; + $new->uri = $this->uri->withQuery($query); + + /** @var UriInterface $new */ + return $new; + } + + /** + * @param string $fragment + * @return UriInterface + */ + public function withFragment($fragment): UriInterface + { + $new = clone $this; + $new->uri = $this->uri->withFragment($fragment); + + /** @var UriInterface $new */ + return $new; + } +} diff --git a/system/src/Grav/Framework/Psr7/UploadedFile.php b/system/src/Grav/Framework/Psr7/UploadedFile.php new file mode 100644 index 0000000..d3edac0 --- /dev/null +++ b/system/src/Grav/Framework/Psr7/UploadedFile.php @@ -0,0 +1,70 @@ +uploadedFile = new \Nyholm\Psr7\UploadedFile($streamOrFile, $size, $errorStatus, $clientFilename, $clientMediaType); + } + + /** + * @param array $meta + * @return $this + */ + public function setMeta(array $meta) + { + $this->meta = $meta; + + return $this; + } + + /** + * @param array $meta + * @return $this + */ + public function addMeta(array $meta) + { + $this->meta = array_merge($this->meta, $meta); + + return $this; + } + + /** + * @return array + */ + public function getMeta(): array + { + return $this->meta; + } +} diff --git a/system/src/Grav/Framework/Psr7/Uri.php b/system/src/Grav/Framework/Psr7/Uri.php new file mode 100644 index 0000000..631268d --- /dev/null +++ b/system/src/Grav/Framework/Psr7/Uri.php @@ -0,0 +1,135 @@ +uri = new \Nyholm\Psr7\Uri($uri); + } + + /** + * @return array + */ + public function getQueryParams(): array + { + return UriFactory::parseQuery($this->getQuery()); + } + + /** + * @param array $params + * @return UriInterface + */ + public function withQueryParams(array $params): UriInterface + { + $query = UriFactory::buildQuery($params); + + return $this->withQuery($query); + } + + /** + * Whether the URI has the default port of the current scheme. + * + * `$uri->getPort()` may return the standard port. This method can be used for some non-http/https Uri. + * + * @return bool + */ + public function isDefaultPort(): bool + { + return $this->getPort() === null || GuzzleUri::isDefaultPort($this); + } + + /** + * Whether the URI is absolute, i.e. it has a scheme. + * + * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true + * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative + * to another URI, the base URI. Relative references can be divided into several forms: + * - network-path references, e.g. '//example.com/path' + * - absolute-path references, e.g. '/path' + * - relative-path references, e.g. 'subpath' + * + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-4 + */ + public function isAbsolute(): bool + { + return GuzzleUri::isAbsolute($this); + } + + /** + * Whether the URI is a network-path reference. + * + * A relative reference that begins with two slash characters is termed an network-path reference. + * + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public function isNetworkPathReference(): bool + { + return GuzzleUri::isNetworkPathReference($this); + } + + /** + * Whether the URI is a absolute-path reference. + * + * A relative reference that begins with a single slash character is termed an absolute-path reference. + * + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public function isAbsolutePathReference(): bool + { + return GuzzleUri::isAbsolutePathReference($this); + } + + /** + * Whether the URI is a relative-path reference. + * + * A relative reference that does not begin with a slash character is termed a relative-path reference. + * + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public function isRelativePathReference(): bool + { + return GuzzleUri::isRelativePathReference($this); + } + + /** + * Whether the URI is a same-document reference. + * + * A same-document reference refers to a URI that is, aside from its fragment + * component, identical to the base URI. When no base URI is given, only an empty + * URI reference (apart from its fragment) is considered a same-document reference. + * + * @param UriInterface|null $base An optional base URI to compare against + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-4.4 + */ + public function isSameDocumentReference(UriInterface $base = null): bool + { + return GuzzleUri::isSameDocumentReference($this, $base); + } +} diff --git a/system/src/Grav/Framework/Relationships/Relationships.php b/system/src/Grav/Framework/Relationships/Relationships.php new file mode 100644 index 0000000..6485682 --- /dev/null +++ b/system/src/Grav/Framework/Relationships/Relationships.php @@ -0,0 +1,217 @@ + + */ +class Relationships implements RelationshipsInterface +{ + /** @var P */ + protected $parent; + /** @var array */ + protected $options; + + /** @var RelationshipInterface[] */ + protected $relationships; + + /** + * Relationships constructor. + * @param P $parent + * @param array $options + */ + public function __construct(IdentifierInterface $parent, array $options) + { + $this->parent = $parent; + $this->options = $options; + $this->relationships = []; + } + + /** + * @return bool + * @phpstan-pure + */ + public function isModified(): bool + { + return !empty($this->getModified()); + } + + /** + * @return RelationshipInterface[] + * @phpstan-pure + */ + public function getModified(): array + { + $list = []; + foreach ($this->relationships as $name => $relationship) { + if ($relationship->isModified()) { + $list[$name] = $relationship; + } + } + + return $list; + } + + /** + * @return int + * @phpstan-pure + */ + public function count(): int + { + return count($this->options); + } + + /** + * @param string $offset + * @return bool + * @phpstan-pure + */ + public function offsetExists($offset): bool + { + return isset($this->options[$offset]); + } + + /** + * @param string $offset + * @return RelationshipInterface|null + */ + public function offsetGet($offset): ?RelationshipInterface + { + if (!isset($this->relationships[$offset])) { + $options = $this->options[$offset] ?? null; + if (null === $options) { + return null; + } + + $this->relationships[$offset] = $this->createRelationship($offset, $options); + } + + return $this->relationships[$offset]; + } + + /** + * @param string $offset + * @param mixed $value + * @return never-return + */ + public function offsetSet($offset, $value) + { + throw new RuntimeException('Setting relationship is not supported', 500); + } + + /** + * @param string $offset + * @return never-return + */ + public function offsetUnset($offset) + { + throw new RuntimeException('Removing relationship is not allowed', 500); + } + + /** + * @return RelationshipInterface|null + */ + public function current(): ?RelationshipInterface + { + $name = key($this->options); + if ($name === null) { + return null; + } + + return $this->offsetGet($name); + } + + /** + * @return string + * @phpstan-pure + */ + public function key(): string + { + return key($this->options); + } + + /** + * @return void + * @phpstan-pure + */ + public function next(): void + { + next($this->options); + } + + /** + * @return void + * @phpstan-pure + */ + public function rewind(): void + { + reset($this->options); + } + + /** + * @return bool + * @phpstan-pure + */ + public function valid(): bool + { + return key($this->options) !== null; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $list = []; + foreach ($this as $name => $relationship) { + $list[$name] = $relationship->jsonSerialize(); + } + + return $list; + } + + /** + * @param string $name + * @param array $options + * @return ToOneRelationship|ToManyRelationship + */ + private function createRelationship(string $name, array $options): RelationshipInterface + { + $data = null; + + $parent = $this->parent; + if ($parent instanceof FlexIdentifier) { + $object = $parent->getObject(); + if (!method_exists($object, 'initRelationship')) { + throw new RuntimeException(sprintf('Bad relationship %s', $name), 500); + } + + $data = $object->initRelationship($name); + } + + $cardinality = $options['cardinality'] ?? ''; + switch ($cardinality) { + case 'to-one': + $relationship = new ToOneRelationship($parent, $name, $options, $data); + break; + case 'to-many': + $relationship = new ToManyRelationship($parent, $name, $options, $data ?? []); + break; + default: + throw new RuntimeException(sprintf('Bad relationship cardinality %s', $cardinality), 500); + } + + return $relationship; + } +} diff --git a/system/src/Grav/Framework/Relationships/ToManyRelationship.php b/system/src/Grav/Framework/Relationships/ToManyRelationship.php new file mode 100644 index 0000000..3ea501b --- /dev/null +++ b/system/src/Grav/Framework/Relationships/ToManyRelationship.php @@ -0,0 +1,259 @@ + + */ +class ToManyRelationship implements ToManyRelationshipInterface +{ + /** @template-use RelationshipTrait */ + use RelationshipTrait; + use Serializable; + + /** @var IdentifierInterface[] */ + protected $identifiers = []; + + /** + * ToManyRelationship constructor. + * @param string $name + * @param IdentifierInterface $parent + * @param iterable $identifiers + */ + public function __construct(IdentifierInterface $parent, string $name, array $options, iterable $identifiers = []) + { + $this->parent = $parent; + $this->name = $name; + + $this->parseOptions($options); + $this->addIdentifiers($identifiers); + + $this->modified = false; + } + + /** + * @return string + * @phpstan-pure + */ + public function getCardinality(): string + { + return 'to-many'; + } + + /** + * @return int + * @phpstan-pure + */ + public function count(): int + { + return count($this->identifiers); + } + + /** + * @return array + */ + public function fetch(): array + { + $list = []; + foreach ($this->identifiers as $identifier) { + if (is_callable([$identifier, 'getObject'])) { + $identifier = $identifier->getObject(); + } + $list[] = $identifier; + } + + return $list; + } + + /** + * @param string $id + * @param string|null $type + * @return bool + * @phpstan-pure + */ + public function has(string $id, string $type = null): bool + { + return $this->getIdentifier($id, $type) !== null; + } + + /** + * @param positive-int $pos + * @return IdentifierInterface|null + */ + public function getNthIdentifier(int $pos): ?IdentifierInterface + { + $items = array_keys($this->identifiers); + $key = $items[$pos - 1] ?? null; + if (null === $key) { + return null; + } + + return $this->identifiers[$key] ?? null; + } + + /** + * @param string $id + * @param string|null $type + * @return IdentifierInterface|null + * @phpstan-pure + */ + public function getIdentifier(string $id, string $type = null): ?IdentifierInterface + { + if (null === $type) { + $type = $this->getType(); + } + + if ($type === 'media' && !str_contains($id, '/')) { + $name = $this->name; + $id = $this->parent->getType() . '/' . $this->parent->getId() . '/'. $name . '/' . $id; + } + + $key = "{$type}/{$id}"; + + return $this->identifiers[$key] ?? null; + } + + /** + * @param string $id + * @param string|null $type + * @return T|null + */ + public function getObject(string $id, string $type = null): ?object + { + $identifier = $this->getIdentifier($id, $type); + if ($identifier && is_callable([$identifier, 'getObject'])) { + $identifier = $identifier->getObject(); + } + + return $identifier; + } + + /** + * @param IdentifierInterface $identifier + * @return bool + */ + public function addIdentifier(IdentifierInterface $identifier): bool + { + return $this->addIdentifiers([$identifier]); + } + + /** + * @param IdentifierInterface|null $identifier + * @return bool + */ + public function removeIdentifier(IdentifierInterface $identifier = null): bool + { + return !$identifier || $this->removeIdentifiers([$identifier]); + } + + /** + * @param iterable $identifiers + * @return bool + */ + public function addIdentifiers(iterable $identifiers): bool + { + foreach ($identifiers as $identifier) { + $type = $identifier->getType(); + $id = $identifier->getId(); + $key = "{$type}/{$id}"; + + $this->identifiers[$key] = $this->checkIdentifier($identifier); + $this->modified = true; + } + + return true; + } + + /** + * @param iterable $identifiers + * @return bool + */ + public function replaceIdentifiers(iterable $identifiers): bool + { + $this->identifiers = []; + $this->modified = true; + + return $this->addIdentifiers($identifiers); + } + + /** + * @param iterable $identifiers + * @return bool + */ + public function removeIdentifiers(iterable $identifiers): bool + { + foreach ($identifiers as $identifier) { + $type = $identifier->getType(); + $id = $identifier->getId(); + $key = "{$type}/{$id}"; + + unset($this->identifiers[$key]); + $this->modified = true; + } + + return true; + } + + /** + * @return iterable + * @phpstan-pure + */ + public function getIterator(): iterable + { + return new ArrayIterator($this->identifiers); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $list = []; + foreach ($this->getIterator() as $item) { + $list[] = $item->jsonSerialize(); + } + + return $list; + } + + /** + * @return array + */ + public function __serialize(): array + { + return [ + 'parent' => $this->parent, + 'name' => $this->name, + 'type' => $this->type, + 'options' => $this->options, + 'modified' => $this->modified, + 'identifiers' => $this->identifiers, + ]; + } + + /** + * @param array $data + * @return void + */ + public function __unserialize(array $data): void + { + $this->parent = $data['parent']; + $this->name = $data['name']; + $this->type = $data['type']; + $this->options = $data['options']; + $this->modified = $data['modified']; + $this->identifiers = $data['identifiers']; + } +} diff --git a/system/src/Grav/Framework/Relationships/ToOneRelationship.php b/system/src/Grav/Framework/Relationships/ToOneRelationship.php new file mode 100644 index 0000000..9b09651 --- /dev/null +++ b/system/src/Grav/Framework/Relationships/ToOneRelationship.php @@ -0,0 +1,207 @@ + + */ +class ToOneRelationship implements ToOneRelationshipInterface +{ + /** @template-use RelationshipTrait */ + use RelationshipTrait; + use Serializable; + + /** @var IdentifierInterface|null */ + protected $identifier = null; + + public function __construct(IdentifierInterface $parent, string $name, array $options, IdentifierInterface $identifier = null) + { + $this->parent = $parent; + $this->name = $name; + + $this->parseOptions($options); + $this->replaceIdentifier($identifier); + + $this->modified = false; + } + + /** + * @return string + * @phpstan-pure + */ + public function getCardinality(): string + { + return 'to-one'; + } + + /** + * @return int + * @phpstan-pure + */ + public function count(): int + { + return $this->identifier ? 1 : 0; + } + + /** + * @return object|null + */ + public function fetch(): ?object + { + $identifier = $this->identifier; + if (is_callable([$identifier, 'getObject'])) { + $identifier = $identifier->getObject(); + } + + return $identifier; + } + + + /** + * @param string|null $id + * @param string|null $type + * @return bool + * @phpstan-pure + */ + public function has(string $id = null, string $type = null): bool + { + return $this->getIdentifier($id, $type) !== null; + } + + /** + * @param string|null $id + * @param string|null $type + * @return IdentifierInterface|null + * @phpstan-pure + */ + public function getIdentifier(string $id = null, string $type = null): ?IdentifierInterface + { + if ($id && $this->getType() === 'media' && !str_contains($id, '/')) { + $name = $this->name; + $id = $this->parent->getType() . '/' . $this->parent->getId() . '/'. $name . '/' . $id; + } + + $identifier = $this->identifier ?? null; + if (null === $identifier || ($type && $type !== $identifier->getType()) || ($id && $id !== $identifier->getId())) { + return null; + } + + return $identifier; + } + + /** + * @param string|null $id + * @param string|null $type + * @return T|null + */ + public function getObject(string $id = null, string $type = null): ?object + { + $identifier = $this->getIdentifier($id, $type); + if ($identifier && is_callable([$identifier, 'getObject'])) { + $identifier = $identifier->getObject(); + } + + return $identifier; + } + + /** + * @param IdentifierInterface $identifier + * @return bool + */ + public function addIdentifier(IdentifierInterface $identifier): bool + { + $this->identifier = $this->checkIdentifier($identifier); + $this->modified = true; + + return true; + } + + /** + * @param IdentifierInterface|null $identifier + * @return bool + */ + public function replaceIdentifier(IdentifierInterface $identifier = null): bool + { + if ($identifier === null) { + $this->identifier = null; + $this->modified = true; + + return true; + } + + return $this->addIdentifier($identifier); + } + + /** + * @param IdentifierInterface|null $identifier + * @return bool + */ + public function removeIdentifier(IdentifierInterface $identifier = null): bool + { + if (null === $identifier || $this->has($identifier->getId(), $identifier->getType())) { + $this->identifier = null; + $this->modified = true; + + return true; + } + + return false; + } + + /** + * @return iterable + * @phpstan-pure + */ + public function getIterator(): iterable + { + return new ArrayIterator((array)$this->identifier); + } + + /** + * @return array|null + */ + public function jsonSerialize(): ?array + { + return $this->identifier ? $this->identifier->jsonSerialize() : null; + } + + /** + * @return array + */ + public function __serialize(): array + { + return [ + 'parent' => $this->parent, + 'name' => $this->name, + 'type' => $this->type, + 'options' => $this->options, + 'modified' => $this->modified, + 'identifier' => $this->identifier, + ]; + } + + /** + * @param array $data + * @return void + */ + public function __unserialize(array $data): void + { + $this->parent = $data['parent']; + $this->name = $data['name']; + $this->type = $data['type']; + $this->options = $data['options']; + $this->modified = $data['modified']; + $this->identifier = $data['identifier']; + } +} diff --git a/system/src/Grav/Framework/Relationships/Traits/RelationshipTrait.php b/system/src/Grav/Framework/Relationships/Traits/RelationshipTrait.php new file mode 100644 index 0000000..dbe146f --- /dev/null +++ b/system/src/Grav/Framework/Relationships/Traits/RelationshipTrait.php @@ -0,0 +1,128 @@ +name; + } + + /** + * @return string + * @phpstan-pure + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return bool + * @phpstan-pure + */ + public function isModified(): bool + { + return $this->modified; + } + + /** + * @return IdentifierInterface + * @phpstan-pure + */ + public function getParent(): IdentifierInterface + { + return $this->parent; + } + + /** + * @param IdentifierInterface $identifier + * @return bool + * @phpstan-pure + */ + public function hasIdentifier(IdentifierInterface $identifier): bool + { + return $this->getIdentifier($identifier->getId(), $identifier->getType()) !== null; + } + + /** + * @return int + * @phpstan-pure + */ + abstract public function count(): int; + + /** + * @return void + * @phpstan-pure + */ + public function check(): void + { + $min = $this->options['min'] ?? 0; + $max = $this->options['max'] ?? 0; + + if ($min || $max) { + $count = $this->count(); + if ($min && $count < $min) { + throw new RuntimeException(sprintf('%s relationship has too few objects in it', $this->name)); + } + if ($max && $count > $max) { + throw new RuntimeException(sprintf('%s relationship has too many objects in it', $this->name)); + } + } + } + + /** + * @param IdentifierInterface $identifier + * @return IdentifierInterface + */ + private function checkIdentifier(IdentifierInterface $identifier): IdentifierInterface + { + if ($this->type !== $identifier->getType()) { + throw new RuntimeException(sprintf('Bad identifier type %s', $identifier->getType())); + } + + if (get_class($identifier) !== Identifier::class) { + return $identifier; + } + + if ($this->type === 'media') { + return new MediaIdentifier($identifier->getId()); + } + + return new FlexIdentifier($identifier->getId(), $identifier->getType()); + } + + private function parseOptions(array $options): void + { + $this->type = $options['type']; + $this->options = $options; + } +} diff --git a/system/src/Grav/Framework/RequestHandler/Exception/InvalidArgumentException.php b/system/src/Grav/Framework/RequestHandler/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..d7ce203 --- /dev/null +++ b/system/src/Grav/Framework/RequestHandler/Exception/InvalidArgumentException.php @@ -0,0 +1,49 @@ +invalidMiddleware = $invalidMiddleware; + } + + /** + * Return the invalid middleware + * + * @return mixed|null + */ + public function getInvalidMiddleware() + { + return $this->invalidMiddleware; + } +} diff --git a/system/src/Grav/Framework/RequestHandler/Exception/NotFoundException.php b/system/src/Grav/Framework/RequestHandler/Exception/NotFoundException.php new file mode 100644 index 0000000..b8662f2 --- /dev/null +++ b/system/src/Grav/Framework/RequestHandler/Exception/NotFoundException.php @@ -0,0 +1,37 @@ +getMethod()), ['PUT', 'PATCH', 'DELETE'])) { + parent::__construct($request, 'Method Not Allowed', 405, $previous); + } else { + parent::__construct($request, 'Not Found', 404, $previous); + } + } +} diff --git a/system/src/Grav/Framework/RequestHandler/Exception/NotHandledException.php b/system/src/Grav/Framework/RequestHandler/Exception/NotHandledException.php new file mode 100644 index 0000000..9c16782 --- /dev/null +++ b/system/src/Grav/Framework/RequestHandler/Exception/NotHandledException.php @@ -0,0 +1,20 @@ + 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Time-out', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Large', + 415 => 'Unsupported Media Type', + 416 => 'Requested range not satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', + 419 => 'Page Expired', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Unordered Collection', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 451 => 'Unavailable For Legal Reasons', + + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Time-out', + 505 => 'HTTP Version not supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 511 => 'Network Authentication Required', + ]; + + /** @var ServerRequestInterface */ + private $request; + + /** + * @param ServerRequestInterface $request + * @param string $message + * @param int $code + * @param Throwable|null $previous + */ + public function __construct(ServerRequestInterface $request, string $message, int $code = 500, Throwable $previous = null) + { + $this->request = $request; + + parent::__construct($message, $code, $previous); + } + + /** + * @return ServerRequestInterface + */ + public function getRequest(): ServerRequestInterface + { + return $this->request; + } + + public function getHttpCode(): int + { + $code = $this->getCode(); + + return isset(self::$phrases[$code]) ? $code : 500; + } + + public function getHttpReason(): ?string + { + return self::$phrases[$this->getCode()] ?? self::$phrases[500]; + } +} diff --git a/system/src/Grav/Framework/RequestHandler/Middlewares/Exceptions.php b/system/src/Grav/Framework/RequestHandler/Middlewares/Exceptions.php new file mode 100644 index 0000000..f07abbe --- /dev/null +++ b/system/src/Grav/Framework/RequestHandler/Middlewares/Exceptions.php @@ -0,0 +1,78 @@ +handle($request); + } catch (Throwable $exception) { + $code = $exception->getCode(); + if ($exception instanceof ValidationException) { + $message = $exception->getMessage(); + } else { + $message = htmlspecialchars($exception->getMessage(), ENT_QUOTES | ENT_HTML5, 'UTF-8'); + } + + $extra = $exception instanceof JsonSerializable ? $exception->jsonSerialize() : []; + + $response = [ + 'code' => $code, + 'status' => 'error', + 'message' => $message, + 'error' => [ + 'code' => $code, + 'message' => $message, + ] + $extra + ]; + + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + if ($debugger->enabled()) { + $response['error'] += [ + 'type' => get_class($exception), + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'trace' => explode("\n", $exception->getTraceAsString()), + ]; + } + + /** @var string $json */ + $json = json_encode($response, JSON_THROW_ON_ERROR); + + return new Response($code ?: 500, ['Content-Type' => 'application/json'], $json); + } + } +} diff --git a/system/src/Grav/Framework/RequestHandler/Middlewares/MultipartRequestSupport.php b/system/src/Grav/Framework/RequestHandler/Middlewares/MultipartRequestSupport.php new file mode 100644 index 0000000..35231dd --- /dev/null +++ b/system/src/Grav/Framework/RequestHandler/Middlewares/MultipartRequestSupport.php @@ -0,0 +1,123 @@ +getHeaderLine('content-type'); + $method = $request->getMethod(); + if (!str_starts_with($contentType, 'multipart/form-data') || !in_array($method, ['PUT', 'PATH'], true)) { + return $handler->handle($request); + } + + $boundary = explode('; boundary=', $contentType, 2)[1] ?? ''; + $parts = explode("--{$boundary}", $request->getBody()->getContents()); + $parts = array_slice($parts, 1, count($parts) - 2); + + $params = []; + $files = []; + foreach ($parts as $part) { + $this->processPart($params, $files, $part); + } + + return $handler->handle($request->withParsedBody($params)->withUploadedFiles($files)); + } + + /** + * @param array $params + * @param array $files + * @param string $part + * @return void + */ + protected function processPart(array &$params, array &$files, string $part): void + { + $part = ltrim($part, "\r\n"); + [$rawHeaders, $body] = explode("\r\n\r\n", $part, 2); + + // Parse headers. + $rawHeaders = explode("\r\n", $rawHeaders); + $headers = array_reduce( + $rawHeaders, + static function (array $headers, $header) { + [$name, $value] = explode(':', $header); + $headers[strtolower($name)] = ltrim($value, ' '); + + return $headers; + }, + [] + ); + + if (!isset($headers['content-disposition'])) { + return; + } + + // Parse content disposition header. + $contentDisposition = $headers['content-disposition']; + preg_match('/^(.+); *name="([^"]+)"(; *filename="([^"]+)")?/', $contentDisposition, $matches); + $name = $matches[2]; + $filename = $matches[4] ?? null; + + if ($filename !== null) { + $stream = Stream::create($body); + $this->addFile($files, $name, new UploadedFile($stream, strlen($body), UPLOAD_ERR_OK, $filename, $headers['content-type'] ?? null)); + } elseif (strpos($contentDisposition, 'filename') !== false) { + // Not uploaded file. + $stream = Stream::create(''); + $this->addFile($files, $name, new UploadedFile($stream, 0, UPLOAD_ERR_NO_FILE)); + } else { + // Regular field. + $params[$name] = substr($body, 0, -2); + } + } + + /** + * @param array $files + * @param string $name + * @param UploadedFileInterface $file + * @return void + */ + protected function addFile(array &$files, string $name, UploadedFileInterface $file): void + { + if (strpos($name, '[]') === strlen($name) - 2) { + $name = substr($name, 0, -2); + + if (isset($files[$name]) && is_array($files[$name])) { + $files[$name][] = $file; + } else { + $files[$name] = [$file]; + } + } else { + $files[$name] = $file; + } + } +} diff --git a/system/src/Grav/Framework/RequestHandler/RequestHandler.php b/system/src/Grav/Framework/RequestHandler/RequestHandler.php new file mode 100644 index 0000000..e82d3d6 --- /dev/null +++ b/system/src/Grav/Framework/RequestHandler/RequestHandler.php @@ -0,0 +1,80 @@ +middleware = $middleware; + $this->handler = $default; + $this->container = $container; + } + + /** + * Add callable initializing Middleware that will be executed as soon as possible. + * + * @param string $name + * @param callable $callable + * @return $this + */ + public function addCallable(string $name, callable $callable): self + { + if (null !== $this->container) { + assert($this->container instanceof Container); + $this->container[$name] = $callable; + } + + array_unshift($this->middleware, $name); + + return $this; + } + + /** + * Add Middleware that will be executed as soon as possible. + * + * @param string $name + * @param MiddlewareInterface $middleware + * @return $this + */ + public function addMiddleware(string $name, MiddlewareInterface $middleware): self + { + if (null !== $this->container) { + assert($this->container instanceof Container); + $this->container[$name] = $middleware; + } + + array_unshift($this->middleware, $name); + + return $this; + } +} diff --git a/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php b/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php new file mode 100644 index 0000000..6efefe8 --- /dev/null +++ b/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php @@ -0,0 +1,64 @@ + */ + protected $middleware; + + /** @var callable */ + protected $handler; + + /** @var ContainerInterface|null */ + protected $container; + + /** + * {@inheritdoc} + * @throws InvalidArgumentException + */ + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = array_shift($this->middleware); + + // Use default callable if there is no middleware. + if ($middleware === null) { + return call_user_func($this->handler, $request); + } + + if ($middleware instanceof MiddlewareInterface) { + return $middleware->process($request, clone $this); + } + + if (null === $this->container || !$this->container->has($middleware)) { + throw new InvalidArgumentException( + sprintf('The middleware is not a valid %s and is not passed in the Container', MiddlewareInterface::class), + $middleware + ); + } + + array_unshift($this->middleware, $this->container->get($middleware)); + + return $this->handle($request); + } +} diff --git a/system/src/Grav/Framework/Route/Route.php b/system/src/Grav/Framework/Route/Route.php new file mode 100644 index 0000000..4129693 --- /dev/null +++ b/system/src/Grav/Framework/Route/Route.php @@ -0,0 +1,452 @@ +initParts($parts); + } + + /** + * @return array + */ + public function getParts() + { + return [ + 'path' => $this->getUriPath(true), + 'query' => $this->getUriQuery(), + 'grav' => [ + 'root' => $this->root, + 'language' => $this->language, + 'route' => $this->route, + 'extension' => $this->extension, + 'grav_params' => $this->gravParams, + 'query_params' => $this->queryParams, + ], + ]; + } + + /** + * @return string + */ + public function getRootPrefix() + { + return $this->root; + } + + /** + * @return string + */ + public function getLanguage() + { + return $this->language; + } + + /** + * @return string + */ + public function getLanguagePrefix() + { + return $this->language !== '' ? '/' . $this->language : ''; + } + + /** + * @param string|null $language + * @return string + */ + public function getBase(string $language = null): string + { + $parts = [$this->root]; + + if (null === $language) { + $language = $this->language; + } + + if ($language !== '') { + $parts[] = $language; + } + + return implode('/', $parts); + } + + /** + * @param int $offset + * @param int|null $length + * @return string + */ + public function getRoute($offset = 0, $length = null) + { + if ($offset !== 0 || $length !== null) { + return ($offset === 0 ? '/' : '') . implode('/', $this->getRouteParts($offset, $length)); + } + + return '/' . $this->route; + } + + /** + * @return string + */ + public function getExtension() + { + return $this->extension; + } + + /** + * @param int $offset + * @param int|null $length + * @return array + */ + public function getRouteParts($offset = 0, $length = null) + { + $parts = explode('/', $this->route); + + if ($offset !== 0 || $length !== null) { + $parts = array_slice($parts, $offset, $length); + } + + return $parts; + } + + /** + * Return array of both query and Grav parameters. + * + * If a parameter exists in both, prefer Grav parameter. + * + * @return array + */ + public function getParams() + { + return $this->gravParams + $this->queryParams; + } + + /** + * @return array + */ + public function getGravParams() + { + return $this->gravParams; + } + + /** + * @return array + */ + public function getQueryParams() + { + return $this->queryParams; + } + + /** + * Return value of the parameter, looking into both Grav parameters and query parameters. + * + * If the parameter exists in both, return Grav parameter. + * + * @param string $param + * @return string|array|null + */ + public function getParam($param) + { + return $this->getGravParam($param) ?? $this->getQueryParam($param); + } + + /** + * @param string $param + * @return string|null + */ + public function getGravParam($param) + { + return $this->gravParams[$param] ?? null; + } + + /** + * @param string $param + * @return string|array|null + */ + public function getQueryParam($param) + { + return $this->queryParams[$param] ?? null; + } + + /** + * Allow the ability to set the route to something else + * + * @param string $route + * @return Route + */ + public function withRoute($route) + { + $new = $this->copy(); + $new->route = $route; + + return $new; + } + + /** + * Allow the ability to set the root to something else + * + * @param string $root + * @return Route + */ + public function withRoot($root) + { + $new = $this->copy(); + $new->root = $root; + + return $new; + } + + /** + * @param string|null $language + * @return Route + */ + public function withLanguage($language) + { + $new = $this->copy(); + $new->language = $language ?? ''; + + return $new; + } + + /** + * @param string $path + * @return Route + */ + public function withAddedPath($path) + { + $new = $this->copy(); + $new->route .= '/' . ltrim($path, '/'); + + return $new; + } + + /** + * @param string $extension + * @return Route + */ + public function withExtension($extension) + { + $new = $this->copy(); + $new->extension = $extension; + + return $new; + } + + /** + * @param string $param + * @param mixed $value + * @return Route + */ + public function withGravParam($param, $value) + { + return $this->withParam('gravParams', $param, null !== $value ? (string)$value : null); + } + + /** + * @param string $param + * @param mixed $value + * @return Route + */ + public function withQueryParam($param, $value) + { + return $this->withParam('queryParams', $param, $value); + } + + /** + * @return Route + */ + public function withoutParams() + { + return $this->withoutGravParams()->withoutQueryParams(); + } + + /** + * @return Route + */ + public function withoutGravParams() + { + $new = $this->copy(); + $new->gravParams = []; + + return $new; + } + + /** + * @return Route + */ + public function withoutQueryParams() + { + $new = $this->copy(); + $new->queryParams = []; + + return $new; + } + + /** + * @return Uri + */ + public function getUri() + { + return UriFactory::createFromParts($this->getParts()); + } + + /** + * @param bool $includeRoot + * @return string + */ + public function toString(bool $includeRoot = false) + { + $url = $this->getUriPath($includeRoot); + + if ($this->queryParams) { + $url .= '?' . $this->getUriQuery(); + } + + return rtrim($url,'/'); + } + + /** + * @return string + * @deprecated 1.6 Use ->toString(true) or ->getUri() instead. + */ + #[\ReturnTypeWillChange] + public function __toString() + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() will change in the future to return route, not relative url: use ->toString(true) or ->getUri() instead.', E_USER_DEPRECATED); + + return $this->toString(true); + } + + /** + * @param string $type + * @param string $param + * @param mixed $value + * @return Route + */ + protected function withParam($type, $param, $value) + { + $values = $this->{$type} ?? []; + $oldValue = $values[$param] ?? null; + + if ($oldValue === $value) { + return $this; + } + + $new = $this->copy(); + if ($value === null) { + unset($values[$param]); + } else { + $values[$param] = $value; + } + + $new->{$type} = $values; + + return $new; + } + + /** + * @return Route + */ + protected function copy() + { + return clone $this; + } + + /** + * @param bool $includeRoot + * @return string + */ + protected function getUriPath($includeRoot = false) + { + $parts = $includeRoot ? [$this->root] : ['']; + + if ($this->language !== '') { + $parts[] = $this->language; + } + + $parts[] = $this->extension ? $this->route . '.' . $this->extension : $this->route; + + + if ($this->gravParams) { + $parts[] = RouteFactory::buildParams($this->gravParams); + } + + return implode('/', $parts); + } + + /** + * @return string + */ + protected function getUriQuery() + { + return UriFactory::buildQuery($this->queryParams); + } + + /** + * @param array $parts + * @return void + */ + protected function initParts(array $parts) + { + if (isset($parts['grav'])) { + $gravParts = $parts['grav']; + $this->root = $gravParts['root']; + $this->language = $gravParts['language']; + $this->route = $gravParts['route']; + $this->extension = $gravParts['extension'] ?? ''; + $this->gravParams = $gravParts['params'] ?? []; + $this->queryParams = $parts['query_params'] ?? []; + } else { + $this->root = RouteFactory::getRoot(); + $this->language = RouteFactory::getLanguage(); + + $path = $parts['path'] ?? '/'; + if (isset($parts['params'])) { + $this->route = trim(rawurldecode($path), '/'); + $this->gravParams = $parts['params']; + } else { + $this->route = trim(RouteFactory::stripParams($path, true), '/'); + $this->gravParams = RouteFactory::getParams($path); + } + if (isset($parts['query'])) { + $this->queryParams = UriFactory::parseQuery($parts['query']); + } + } + } +} diff --git a/system/src/Grav/Framework/Route/RouteFactory.php b/system/src/Grav/Framework/Route/RouteFactory.php new file mode 100644 index 0000000..8e654a1 --- /dev/null +++ b/system/src/Grav/Framework/Route/RouteFactory.php @@ -0,0 +1,236 @@ +toArray(); + $parts += [ + 'grav' => [] + ]; + $path = $parts['path'] ?? ''; + $parts['grav'] += [ + 'root' => self::$root, + 'language' => self::$language, + 'route' => trim($path, '/'), + 'params' => $parts['params'] ?? [], + ]; + + return static::createFromParts($parts); + } + + /** + * @param string $path + * @return Route + */ + public static function createFromString(string $path): Route + { + $path = ltrim($path, '/'); + if (self::$language && mb_strpos($path, self::$language) === 0) { + $path = ltrim(mb_substr($path, mb_strlen(self::$language)), '/'); + } + + $parts = [ + 'path' => $path, + 'query' => '', + 'query_params' => [], + 'grav' => [ + 'root' => self::$root, + 'language' => self::$language, + 'route' => static::trimParams($path), + 'params' => static::getParams($path) + ], + ]; + + return new Route($parts); + } + + /** + * @return string + */ + public static function getRoot(): string + { + return self::$root; + } + + /** + * @param string $root + */ + public static function setRoot($root): void + { + self::$root = rtrim($root, '/'); + } + + /** + * @return string + */ + public static function getLanguage(): string + { + return self::$language; + } + + /** + * @param string $language + */ + public static function setLanguage(string $language): void + { + self::$language = trim($language, '/'); + } + + /** + * @return string + */ + public static function getParamValueDelimiter(): string + { + return self::$delimiter; + } + + /** + * @param string $delimiter + */ + public static function setParamValueDelimiter(string $delimiter): void + { + self::$delimiter = $delimiter ?: ':'; + } + + /** + * @param array $params + * @return string + */ + public static function buildParams(array $params): string + { + if (!$params) { + return ''; + } + + $delimiter = self::$delimiter; + + $output = []; + foreach ($params as $key => $value) { + $output[] = "{$key}{$delimiter}{$value}"; + } + + return implode('/', $output); + } + + /** + * @param string $path + * @param bool $decode + * @return string + */ + public static function stripParams(string $path, bool $decode = false): string + { + $pos = strpos($path, self::$delimiter); + + if ($pos === false) { + return $path; + } + + $path = dirname(substr($path, 0, $pos)); + if ($path === '.') { + return ''; + } + + return $decode ? rawurldecode($path) : $path; + } + + /** + * @param string $path + * @return array + */ + public static function getParams(string $path): array + { + $params = ltrim(substr($path, strlen(static::stripParams($path))), '/'); + + return $params !== '' ? static::parseParams($params) : []; + } + + /** + * @param string $str + * @return string + */ + public static function trimParams(string $str): string + { + if ($str === '') { + return $str; + } + + $delimiter = self::$delimiter; + + /** @var array $params */ + $params = explode('/', $str); + $list = []; + foreach ($params as $param) { + if (mb_strpos($param, $delimiter) === false) { + $list[] = $param; + } + } + + return implode('/', $list); + } + + /** + * @param string $str + * @return array + */ + public static function parseParams(string $str): array + { + if ($str === '') { + return []; + } + + $delimiter = self::$delimiter; + + /** @var array $params */ + $params = explode('/', $str); + $list = []; + foreach ($params as &$param) { + /** @var array $parts */ + $parts = explode($delimiter, $param, 2); + if (isset($parts[1])) { + $var = rawurldecode($parts[0]); + $val = rawurldecode($parts[1]); + $list[$var] = $val; + } + } + + return $list; + } +} diff --git a/system/src/Grav/Framework/Session/Exceptions/SessionException.php b/system/src/Grav/Framework/Session/Exceptions/SessionException.php new file mode 100644 index 0000000..f27fe8a --- /dev/null +++ b/system/src/Grav/Framework/Session/Exceptions/SessionException.php @@ -0,0 +1,20 @@ + $message, 'scope' => $scope]; + + // don't add duplicates + if (!array_key_exists($key, $this->messages)) { + $this->messages[$key] = $item; + } + + return $this; + } + + /** + * Clear message queue. + * + * @param string|null $scope + * @return $this + */ + public function clear(string $scope = null): Messages + { + if ($scope === null) { + if ($this->messages !== []) { + $this->isCleared = true; + $this->messages = []; + } + } else { + foreach ($this->messages as $key => $message) { + if ($message['scope'] === $scope) { + $this->isCleared = true; + unset($this->messages[$key]); + } + } + } + + return $this; + } + + /** + * @return bool + */ + public function isCleared(): bool + { + return $this->isCleared; + } + + /** + * Fetch all messages. + * + * @param string|null $scope + * @return array + */ + public function all(string $scope = null): array + { + if ($scope === null) { + return array_values($this->messages); + } + + $messages = []; + foreach ($this->messages as $message) { + if ($message['scope'] === $scope) { + $messages[] = $message; + } + } + + return $messages; + } + + /** + * Fetch and clear message queue. + * + * @param string|null $scope + * @return array + */ + public function fetch(string $scope = null): array + { + $messages = $this->all($scope); + $this->clear($scope); + + return $messages; + } + + /** + * @return array + */ + public function __serialize(): array + { + return [ + 'messages' => $this->messages + ]; + } + + /** + * @param array $data + * @return void + */ + public function __unserialize(array $data): void + { + $this->messages = $data['messages']; + } +} diff --git a/system/src/Grav/Framework/Session/Session.php b/system/src/Grav/Framework/Session/Session.php new file mode 100644 index 0000000..41732c8 --- /dev/null +++ b/system/src/Grav/Framework/Session/Session.php @@ -0,0 +1,562 @@ +isSessionStarted()) { + session_unset(); + session_destroy(); + } + + // Set default options. + $options += [ + 'cache_limiter' => 'nocache', + 'use_trans_sid' => 0, + 'use_cookies' => 1, + 'lazy_write' => 1, + 'use_strict_mode' => 1 + ]; + + $this->setOptions($options); + + session_register_shutdown(); + + self::$instance = $this; + } + + /** + * @inheritdoc + */ + public function getId() + { + return session_id() ?: null; + } + + /** + * @inheritdoc + */ + public function setId($id) + { + session_id($id); + + return $this; + } + + /** + * @inheritdoc + */ + public function getName() + { + return session_name() ?: null; + } + + /** + * @inheritdoc + */ + public function setName($name) + { + session_name($name); + + return $this; + } + + /** + * @inheritdoc + */ + public function setOptions(array $options) + { + if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) { + return; + } + + $allowedOptions = [ + 'save_path' => true, + 'name' => true, + 'save_handler' => true, + 'gc_probability' => true, + 'gc_divisor' => true, + 'gc_maxlifetime' => true, + 'serialize_handler' => true, + 'cookie_lifetime' => true, + 'cookie_path' => true, + 'cookie_domain' => true, + 'cookie_secure' => true, + 'cookie_httponly' => true, + 'use_strict_mode' => true, + 'use_cookies' => true, + 'use_only_cookies' => true, + 'cookie_samesite' => true, + 'referer_check' => true, + 'cache_limiter' => true, + 'cache_expire' => true, + 'use_trans_sid' => true, + 'trans_sid_tags' => true, + 'trans_sid_hosts' => true, + 'sid_length' => true, + 'sid_bits_per_character' => true, + 'upload_progress.enabled' => true, + 'upload_progress.cleanup' => true, + 'upload_progress.prefix' => true, + 'upload_progress.name' => true, + 'upload_progress.freq' => true, + 'upload_progress.min-freq' => true, + 'lazy_write' => true + ]; + + foreach ($options as $key => $value) { + if (is_array($value)) { + // Allow nested options. + foreach ($value as $key2 => $value2) { + $ckey = "{$key}.{$key2}"; + if (isset($value2, $allowedOptions[$ckey])) { + $this->setOption($ckey, $value2); + } + } + } elseif (isset($value, $allowedOptions[$key])) { + $this->setOption($key, $value); + } + } + } + + /** + * @inheritdoc + */ + public function start($readonly = false) + { + if (\PHP_SAPI === 'cli') { + return $this; + } + + $sessionName = $this->getName(); + if (null === $sessionName) { + return $this; + } + + $sessionExists = isset($_COOKIE[$sessionName]); + + // Protection against invalid session cookie names throwing exception: http://php.net/manual/en/function.session-id.php#116836 + if ($sessionExists && !preg_match('/^[-,a-zA-Z0-9]{1,128}$/', $_COOKIE[$sessionName])) { + unset($_COOKIE[$sessionName]); + $sessionExists = false; + } + + $options = $this->options; + if ($readonly) { + $options['read_and_close'] = '1'; + } + + try { + $success = @session_start($options); + if (!$success) { + $last = error_get_last(); + $error = $last ? $last['message'] : 'Unknown error'; + + throw new RuntimeException($error); + } + + // Handle changing session id. + if ($this->__isset('session_destroyed')) { + $newId = $this->__get('session_new_id'); + if (!$newId || $this->__get('session_destroyed') < time() - 300) { + // Should not happen usually. This could be attack or due to unstable network. Destroy this session. + $this->invalidate(); + + throw new RuntimeException('Obsolete session access.', 500); + } + + // Not fully expired yet. Could be lost cookie by unstable network. Start session with new session id. + session_write_close(); + + // Start session with new session id. + $useStrictMode = $options['use_strict_mode'] ?? 0; + if ($useStrictMode) { + ini_set('session.use_strict_mode', '0'); + } + session_id($newId); + if ($useStrictMode) { + ini_set('session.use_strict_mode', '1'); + } + + $success = @session_start($options); + if (!$success) { + $last = error_get_last(); + $error = $last ? $last['message'] : 'Unknown error'; + + throw new RuntimeException($error); + } + } + } catch (Exception $e) { + throw new SessionException('Failed to start session: ' . $e->getMessage(), 500); + } + + $this->started = true; + $this->onSessionStart(); + + try { + $user = $this->__get('user'); + if ($user && (!$user instanceof UserInterface || (method_exists($user, 'isValid') && !$user->isValid()))) { + throw new RuntimeException('Bad user'); + } + } catch (Throwable $e) { + $this->invalidate(); + throw new SessionException('Invalid User object, session destroyed.', 500); + } + + + // Extend the lifetime of the session. + if ($sessionExists) { + $this->setCookie(); + } + + return $this; + } + + /** + * Regenerate session id but keep the current session information. + * + * Session id must be regenerated on login, logout or after long time has been passed. + * + * @return $this + * @since 1.7 + */ + public function regenerateId() + { + if (!$this->isSessionStarted()) { + return $this; + } + + // TODO: session_create_id() segfaults in PHP 7.3 (PHP bug #73461), remove phpstan rule when removing this one. + if (PHP_VERSION_ID < 70400) { + $newId = 0; + } else { + // Session id creation may fail with some session storages. + $newId = @session_create_id() ?: 0; + } + + // Set destroyed timestamp for the old session as well as pointer to the new id. + $this->__set('session_destroyed', time()); + $this->__set('session_new_id', $newId); + + // Keep the old session alive to avoid lost sessions by unstable network. + if (!$newId) { + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + $debugger->addMessage('Session fixation lost session detection is turned of due to server limitations.', 'warning'); + + session_regenerate_id(false); + } else { + session_write_close(); + + // Start session with new session id. + $useStrictMode = $this->options['use_strict_mode'] ?? 0; + if ($useStrictMode) { + ini_set('session.use_strict_mode', '0'); + } + session_id($newId); + if ($useStrictMode) { + ini_set('session.use_strict_mode', '1'); + } + + $this->removeCookie(); + + $this->onBeforeSessionStart(); + + $success = @session_start($this->options); + if (!$success) { + $last = error_get_last(); + $error = $last ? $last['message'] : 'Unknown error'; + + throw new RuntimeException($error); + } + + $this->onSessionStart(); + } + + // New session does not have these. + $this->__unset('session_destroyed'); + $this->__unset('session_new_id'); + + return $this; + } + + /** + * @inheritdoc + */ + public function invalidate() + { + $name = $this->getName(); + if (null !== $name) { + $this->removeCookie(); + + setcookie( + $name, + '', + $this->getCookieOptions(-42000) + ); + } + + if ($this->isSessionStarted()) { + session_unset(); + session_destroy(); + } + + $this->started = false; + + return $this; + } + + /** + * @inheritdoc + */ + public function close() + { + if ($this->started) { + session_write_close(); + } + + $this->started = false; + + return $this; + } + + /** + * @inheritdoc + */ + public function clear() + { + session_unset(); + + return $this; + } + + /** + * @inheritdoc + */ + public function getAll() + { + return $_SESSION; + } + + /** + * @inheritdoc + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return new ArrayIterator($_SESSION); + } + + /** + * @inheritdoc + */ + public function isStarted() + { + return $this->started; + } + + /** + * @inheritdoc + */ + #[\ReturnTypeWillChange] + public function __isset($name) + { + return isset($_SESSION[$name]); + } + + /** + * @inheritdoc + */ + #[\ReturnTypeWillChange] + public function __get($name) + { + return $_SESSION[$name] ?? null; + } + + /** + * @inheritdoc + */ + #[\ReturnTypeWillChange] + public function __set($name, $value) + { + $_SESSION[$name] = $value; + } + + /** + * @inheritdoc + */ + #[\ReturnTypeWillChange] + public function __unset($name) + { + unset($_SESSION[$name]); + } + + /** + * http://php.net/manual/en/function.session-status.php#113468 + * Check if session is started nicely. + * @return bool + */ + protected function isSessionStarted() + { + return \PHP_SAPI !== 'cli' ? \PHP_SESSION_ACTIVE === session_status() : false; + } + + protected function onBeforeSessionStart(): void + { + } + + protected function onSessionStart(): void + { + } + + /** + * Store something in cookie temporarily. + * + * @param int|null $lifetime + * @return array + */ + public function getCookieOptions(int $lifetime = null): array + { + $params = session_get_cookie_params(); + + return [ + 'expires' => time() + ($lifetime ?? $params['lifetime']), + 'path' => $params['path'], + 'domain' => $params['domain'], + 'secure' => $params['secure'], + 'httponly' => $params['httponly'], + 'samesite' => $params['samesite'] + ]; + } + + /** + * @return void + */ + protected function setCookie(): void + { + $this->removeCookie(); + + $sessionName = $this->getName(); + $sessionId = $this->getId(); + if (null === $sessionName || null === $sessionId) { + return; + } + + setcookie( + $sessionName, + $sessionId, + $this->getCookieOptions() + ); + } + + protected function removeCookie(): void + { + $search = " {$this->getName()}="; + $cookies = []; + $found = false; + + foreach (headers_list() as $header) { + // Identify cookie headers + if (strpos($header, 'Set-Cookie:') === 0) { + // Add all but session cookie(s). + if (!str_contains($header, $search)) { + $cookies[] = $header; + } else { + $found = true; + } + } + } + + // Nothing to do. + if (false === $found) { + return; + } + + // Remove all cookies and put back all but session cookie. + header_remove('Set-Cookie'); + foreach($cookies as $cookie) { + header($cookie, false); + } + } + + /** + * @param string $key + * @param mixed $value + * @return void + */ + protected function setOption($key, $value) + { + if (!is_string($value)) { + if (is_bool($value)) { + $value = $value ? '1' : '0'; + } else { + $value = (string)$value; + } + } + + $this->options[$key] = $value; + ini_set("session.{$key}", $value); + } +} diff --git a/system/src/Grav/Framework/Session/SessionInterface.php b/system/src/Grav/Framework/Session/SessionInterface.php new file mode 100644 index 0000000..d2f8308 --- /dev/null +++ b/system/src/Grav/Framework/Session/SessionInterface.php @@ -0,0 +1,159 @@ + + */ +interface SessionInterface extends IteratorAggregate +{ + /** + * Get current session instance. + * + * @return Session + * @throws RuntimeException + */ + public static function getInstance(); + + /** + * Get session ID + * + * @return string|null Session ID + */ + public function getId(); + + /** + * Set session ID + * + * @param string $id Session ID + * @return $this + */ + public function setId($id); + + /** + * Get session name + * + * @return string|null + */ + public function getName(); + + /** + * Set session name + * + * @param string $name + * @return $this + */ + public function setName($name); + + /** + * Sets session.* ini variables. + * + * @param array $options + * @return void + * @see http://php.net/session.configuration + */ + public function setOptions(array $options); + + /** + * Starts the session storage + * + * @param bool $readonly + * @return $this + * @throws RuntimeException + */ + public function start($readonly = false); + + /** + * Invalidates the current session. + * + * @return $this + */ + public function invalidate(); + + /** + * Force the session to be saved and closed + * + * @return $this + */ + public function close(); + + /** + * Free all session variables. + * + * @return $this + */ + public function clear(); + + /** + * Returns all session variables. + * + * @return array + */ + public function getAll(); + + /** + * Retrieve an external iterator + * + * @return ArrayIterator Return an ArrayIterator of $_SESSION + * @phpstan-return ArrayIterator + */ + #[\ReturnTypeWillChange] + public function getIterator(); + + /** + * Checks if the session was started. + * + * @return bool + */ + public function isStarted(); + + /** + * Checks if session variable is defined. + * + * @param string $name + * @return bool + */ + #[\ReturnTypeWillChange] + public function __isset($name); + + /** + * Returns session variable. + * + * @param string $name + * @return mixed + */ + #[\ReturnTypeWillChange] + public function __get($name); + + /** + * Sets session variable. + * + * @param string $name + * @param mixed $value + * @return void + */ + #[\ReturnTypeWillChange] + public function __set($name, $value); + + /** + * Removes session variable. + * + * @param string $name + * @return void + */ + #[\ReturnTypeWillChange] + public function __unset($name); +} diff --git a/system/src/Grav/Framework/Uri/Uri.php b/system/src/Grav/Framework/Uri/Uri.php new file mode 100644 index 0000000..87ca329 --- /dev/null +++ b/system/src/Grav/Framework/Uri/Uri.php @@ -0,0 +1,216 @@ +initParts($parts); + } + + /** + * @return string + */ + public function getUser() + { + return parent::getUser(); + } + + /** + * @return string + */ + public function getPassword() + { + return parent::getPassword(); + } + + /** + * @return array + */ + public function getParts() + { + return parent::getParts(); + } + + /** + * @return string + */ + public function getUrl() + { + return parent::getUrl(); + } + + /** + * @return string + */ + public function getBaseUrl() + { + return parent::getBaseUrl(); + } + + /** + * @param string $key + * @return string|null + */ + public function getQueryParam($key) + { + $queryParams = $this->getQueryParams(); + + return $queryParams[$key] ?? null; + } + + /** + * @param string $key + * @return UriInterface + */ + public function withoutQueryParam($key) + { + return GuzzleUri::withoutQueryValue($this, $key); + } + + /** + * @param string $key + * @param string|null $value + * @return UriInterface + */ + public function withQueryParam($key, $value) + { + return GuzzleUri::withQueryValue($this, $key, $value); + } + + /** + * @return array + */ + public function getQueryParams() + { + if ($this->queryParams === null) { + $this->queryParams = UriFactory::parseQuery($this->getQuery()); + } + + return $this->queryParams; + } + + /** + * @param array $params + * @return UriInterface + */ + public function withQueryParams(array $params) + { + $query = UriFactory::buildQuery($params); + + return $this->withQuery($query); + } + + /** + * Whether the URI has the default port of the current scheme. + * + * `$uri->getPort()` may return the standard port. This method can be used for some non-http/https Uri. + * + * @return bool + */ + public function isDefaultPort() + { + return $this->getPort() === null || GuzzleUri::isDefaultPort($this); + } + + /** + * Whether the URI is absolute, i.e. it has a scheme. + * + * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true + * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative + * to another URI, the base URI. Relative references can be divided into several forms: + * - network-path references, e.g. '//example.com/path' + * - absolute-path references, e.g. '/path' + * - relative-path references, e.g. 'subpath' + * + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-4 + */ + public function isAbsolute() + { + return GuzzleUri::isAbsolute($this); + } + + /** + * Whether the URI is a network-path reference. + * + * A relative reference that begins with two slash characters is termed an network-path reference. + * + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public function isNetworkPathReference() + { + return GuzzleUri::isNetworkPathReference($this); + } + + /** + * Whether the URI is a absolute-path reference. + * + * A relative reference that begins with a single slash character is termed an absolute-path reference. + * + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public function isAbsolutePathReference() + { + return GuzzleUri::isAbsolutePathReference($this); + } + + /** + * Whether the URI is a relative-path reference. + * + * A relative reference that does not begin with a slash character is termed a relative-path reference. + * + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public function isRelativePathReference() + { + return GuzzleUri::isRelativePathReference($this); + } + + /** + * Whether the URI is a same-document reference. + * + * A same-document reference refers to a URI that is, aside from its fragment + * component, identical to the base URI. When no base URI is given, only an empty + * URI reference (apart from its fragment) is considered a same-document reference. + * + * @param UriInterface|null $base An optional base URI to compare against + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-4.4 + */ + public function isSameDocumentReference(UriInterface $base = null) + { + return GuzzleUri::isSameDocumentReference($this, $base); + } +} diff --git a/system/src/Grav/Framework/Uri/UriFactory.php b/system/src/Grav/Framework/Uri/UriFactory.php new file mode 100644 index 0000000..f112acd --- /dev/null +++ b/system/src/Grav/Framework/Uri/UriFactory.php @@ -0,0 +1,171 @@ + $scheme, + 'user' => $user, + 'pass' => $pass, + 'host' => $host, + 'port' => $port, + 'path' => $path, + 'query' => $query + ]; + } + + /** + * UTF-8 aware parse_url() implementation. + * + * @param string $url + * @return array + * @throws InvalidArgumentException + */ + public static function parseUrl($url) + { + if (!is_string($url)) { + throw new InvalidArgumentException('URL must be a string'); + } + + $encodedUrl = preg_replace_callback( + '%[^:/@?&=#]+%u', + static function ($matches) { + return rawurlencode($matches[0]); + }, + $url + ); + + $parts = is_string($encodedUrl) ? parse_url($encodedUrl) : false; + if ($parts === false) { + throw new InvalidArgumentException("Malformed URL: {$url}"); + } + + return $parts; + } + + /** + * Parse query string and return it as an array. + * + * @param string $query + * @return mixed + */ + public static function parseQuery($query) + { + parse_str($query, $params); + + return $params; + } + + /** + * Build query string from variables. + * + * @param array $params + * @return string + */ + public static function buildQuery(array $params) + { + if (!$params) { + return ''; + } + + $separator = ini_get('arg_separator.output') ?: '&'; + + return http_build_query($params, '', $separator, PHP_QUERY_RFC3986); + } +} diff --git a/system/src/Grav/Framework/Uri/UriPartsFilter.php b/system/src/Grav/Framework/Uri/UriPartsFilter.php new file mode 100644 index 0000000..eda7084 --- /dev/null +++ b/system/src/Grav/Framework/Uri/UriPartsFilter.php @@ -0,0 +1,145 @@ += 0 && $port <= 65535))) { + return $port; + } + + throw new InvalidArgumentException('Uri port must be null or an integer between 0 and 65535'); + } + + /** + * Filter Uri path. + * + * This method percent-encodes all reserved characters in the provided path string. This method + * will NOT double-encode characters that are already percent-encoded. + * + * @param string $path The raw uri path. + * @return string The RFC 3986 percent-encoded uri path. + * @throws InvalidArgumentException If the path is invalid. + * @link http://www.faqs.org/rfcs/rfc3986.html + */ + public static function filterPath($path) + { + if (!is_string($path)) { + throw new InvalidArgumentException('Uri path must be a string'); + } + + return preg_replace_callback( + '/(?:[^a-zA-Z0-9_\-\.~:@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/u', + function ($match) { + return rawurlencode($match[0]); + }, + $path + ) ?? ''; + } + + /** + * Filters the query string or fragment of a URI. + * + * @param string $query The raw uri query string. + * @return string The percent-encoded query string. + * @throws InvalidArgumentException If the query is invalid. + */ + public static function filterQueryOrFragment($query) + { + if (!is_string($query)) { + throw new InvalidArgumentException('Uri query string and fragment must be a string'); + } + + return preg_replace_callback( + '/(?:[^a-zA-Z0-9_\-\.~!\$&\'\(\)\*\+,;=%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/u', + function ($match) { + return rawurlencode($match[0]); + }, + $query + ) ?? ''; + } +} diff --git a/system/src/Grav/Installer/Install.php b/system/src/Grav/Installer/Install.php new file mode 100644 index 0000000..0db7036 --- /dev/null +++ b/system/src/Grav/Installer/Install.php @@ -0,0 +1,400 @@ + [ + 'name' => 'PHP', + 'versions' => [ + '8.1' => '8.1.0', + '8.0' => '8.0.0', + '7.4' => '7.4.1', + '7.3' => '7.3.6', + '' => '8.0.13' + ] + ], + 'grav' => [ + 'name' => 'Grav', + 'versions' => [ + '1.6' => '1.6.0', + '' => '1.6.28' + ] + ], + 'plugins' => [ + 'admin' => [ + 'name' => 'Admin', + 'optional' => true, + 'versions' => [ + '1.9' => '1.9.0', + '' => '1.9.13' + ] + ], + 'email' => [ + 'name' => 'Email', + 'optional' => true, + 'versions' => [ + '3.0' => '3.0.0', + '' => '3.0.10' + ] + ], + 'form' => [ + 'name' => 'Form', + 'optional' => true, + 'versions' => [ + '4.1' => '4.1.0', + '4.0' => '4.0.0', + '3.0' => '3.0.0', + '' => '4.1.2' + ] + ], + 'login' => [ + 'name' => 'Login', + 'optional' => true, + 'versions' => [ + '3.3' => '3.3.0', + '3.0' => '3.0.0', + '' => '3.3.6' + ] + ], + ] + ]; + + /** @var array */ + public $ignores = [ + 'backup', + 'cache', + 'images', + 'logs', + 'tmp', + 'user', + '.htaccess', + 'robots.txt' + ]; + + /** @var array */ + private $classMap = [ + InstallException::class => __DIR__ . '/InstallException.php', + Versions::class => __DIR__ . '/Versions.php', + VersionUpdate::class => __DIR__ . '/VersionUpdate.php', + VersionUpdater::class => __DIR__ . '/VersionUpdater.php', + YamlUpdater::class => __DIR__ . '/YamlUpdater.php', + ]; + + /** @var string|null */ + private $zip; + + /** @var string|null */ + private $location; + + /** @var VersionUpdater|null */ + private $updater; + + /** @var static */ + private static $instance; + + /** + * @return static + */ + public static function instance() + { + if (null === self::$instance) { + self::$instance = new static(); + } + + return self::$instance; + } + + private function __construct() + { + } + + /** + * @param string|null $zip + * @return $this + */ + public function setZip(?string $zip) + { + $this->zip = $zip; + + return $this; + } + + /** + * @param string|null $zip + * @return void + */ + #[\ReturnTypeWillChange] + public function __invoke(?string $zip) + { + $this->zip = $zip; + + $failedRequirements = $this->checkRequirements(); + if ($failedRequirements) { + $error = ['Following requirements have failed:']; + + foreach ($failedRequirements as $name => $req) { + $error[] = "{$req['title']} >= v{$req['minimum']} required, you have v{$req['installed']}"; + } + + $errors = implode("
    \n", $error); + if (\defined('GRAV_CLI') && GRAV_CLI) { + $errors = "\n\n" . strip_tags($errors) . "\n\n"; + $errors .= <<prepare(); + $this->install(); + $this->finalize(); + } + + /** + * NOTE: This method can only be called after $grav['plugins']->init(). + * + * @return array List of failed requirements. If the list is empty, installation can go on. + */ + public function checkRequirements(): array + { + $results = []; + + $this->checkVersion($results, 'php', 'php', $this->requires['php'], PHP_VERSION); + $this->checkVersion($results, 'grav', 'grav', $this->requires['grav'], GRAV_VERSION); + $this->checkPlugins($results, $this->requires['plugins']); + + return $results; + } + + /** + * @return void + * @throws RuntimeException + */ + public function prepare(): void + { + // Locate the new Grav update and the target site from the filesystem. + $location = realpath(__DIR__); + $target = realpath(GRAV_ROOT . '/index.php'); + + if (!$location) { + throw new RuntimeException('Internal Error', 500); + } + + if ($target && dirname($location, 4) === dirname($target)) { + // We cannot copy files into themselves, abort! + throw new RuntimeException('Grav has already been installed here!', 400); + } + + // Load the installer classes. + foreach ($this->classMap as $class_name => $path) { + // Make sure that none of the Grav\Installer classes have been loaded, otherwise installation may fail! + if (class_exists($class_name, false)) { + throw new RuntimeException(sprintf('Cannot update Grav, class %s has already been loaded!', $class_name), 500); + } + + require $path; + } + + $this->legacySupport(); + + $this->location = dirname($location, 4); + + $versions = Versions::instance(USER_DIR . 'config/versions.yaml'); + $this->updater = new VersionUpdater('core/grav', __DIR__ . '/updates', $this->getVersion(), $versions); + + $this->updater->preflight(); + } + + /** + * @return void + * @throws RuntimeException + */ + public function install(): void + { + if (!$this->location) { + throw new RuntimeException('Oops, installer was run without prepare()!', 500); + } + + try { + if (null === $this->updater) { + $versions = Versions::instance(USER_DIR . 'config/versions.yaml'); + $this->updater = new VersionUpdater('core/grav', __DIR__ . '/updates', $this->getVersion(), $versions); + } + + // Update user/config/version.yaml before copying the files to avoid frontend from setting the version schema. + $this->updater->install(); + + Installer::install( + $this->zip ?? '', + GRAV_ROOT, + ['sophisticated' => true, 'overwrite' => true, 'ignore_symlinks' => true, 'ignores' => $this->ignores], + $this->location, + !($this->zip && is_file($this->zip)) + ); + } catch (Exception $e) { + Installer::setError($e->getMessage()); + } + + $errorCode = Installer::lastErrorCode(); + + $success = !(is_string($errorCode) || ($errorCode & (Installer::ZIP_OPEN_ERROR | Installer::ZIP_EXTRACT_ERROR))); + + if (!$success) { + throw new RuntimeException(Installer::lastErrorMsg()); + } + } + + /** + * @return void + * @throws RuntimeException + */ + public function finalize(): void + { + // Finalize can be run without installing Grav first. + if (null === $this->updater) { + $versions = Versions::instance(USER_DIR . 'config/versions.yaml'); + $this->updater = new VersionUpdater('core/grav', __DIR__ . '/updates', GRAV_VERSION, $versions); + $this->updater->install(); + } + + $this->updater->postflight(); + + Cache::clearCache('all'); + + clearstatcache(); + if (function_exists('opcache_reset')) { + @opcache_reset(); + } + } + + /** + * @param array $results + * @param string $type + * @param string $name + * @param array $check + * @param string|null $version + * @return void + */ + protected function checkVersion(array &$results, $type, $name, array $check, $version): void + { + if (null === $version && !empty($check['optional'])) { + return; + } + + $major = $minor = 0; + $versions = $check['versions'] ?? []; + foreach ($versions as $major => $minor) { + if (!$major || version_compare($version ?? '0', $major, '<')) { + continue; + } + + if (version_compare($version ?? '0', $minor, '>=')) { + return; + } + + break; + } + + if (!$major) { + $minor = reset($versions); + } + + $recommended = end($versions); + + if (version_compare($recommended, $minor, '<=')) { + $recommended = null; + } + + $results[$name] = [ + 'type' => $type, + 'name' => $name, + 'title' => $check['name'] ?? $name, + 'installed' => $version, + 'minimum' => $minor, + 'recommended' => $recommended + ]; + } + + /** + * @param array $results + * @param array $plugins + * @return void + */ + protected function checkPlugins(array &$results, array $plugins): void + { + if (!class_exists('Plugins')) { + return; + } + + foreach ($plugins as $name => $check) { + $plugin = Plugins::get($name); + if (!$plugin) { + $this->checkVersion($results, 'plugin', $name, $check, null); + continue; + } + + $blueprint = $plugin->blueprints(); + $version = (string)$blueprint->get('version'); + $check['name'] = ($blueprint->get('name') ?? $check['name'] ?? $name) . ' Plugin'; + $this->checkVersion($results, 'plugin', $name, $check, $version); + } + } + + /** + * @return string + */ + protected function getVersion(): string + { + $definesFile = "{$this->location}/system/defines.php"; + $content = file_get_contents($definesFile); + if (false === $content) { + return ''; + } + + preg_match("/define\('GRAV_VERSION', '([^']+)'\);/mu", $content, $matches); + + return $matches[1] ?? ''; + } + + protected function legacySupport(): void + { + // Support install for Grav 1.6.0 - 1.6.20 by loading the original class from the older version of Grav. + class_exists(\Grav\Console\Cli\CacheCommand::class, true); + } +} diff --git a/system/src/Grav/Installer/InstallException.php b/system/src/Grav/Installer/InstallException.php new file mode 100644 index 0000000..1192849 --- /dev/null +++ b/system/src/Grav/Installer/InstallException.php @@ -0,0 +1,29 @@ +getCode(), $previous); + } +} diff --git a/system/src/Grav/Installer/VersionUpdate.php b/system/src/Grav/Installer/VersionUpdate.php new file mode 100644 index 0000000..1fde783 --- /dev/null +++ b/system/src/Grav/Installer/VersionUpdate.php @@ -0,0 +1,83 @@ +revision = $name; + [$this->version, $this->date, $this->patch] = explode('_', $name); + $this->updater = $updater; + $this->methods = require $file; + } + + public function getRevision(): string + { + return $this->revision; + } + + public function getVersion(): string + { + return $this->version; + } + + public function getDate(): string + { + return $this->date; + } + + public function getPatch(): string + { + return $this->patch; + } + + public function getUpdater(): VersionUpdater + { + return $this->updater; + } + + /** + * Run right before installation. + */ + public function preflight(VersionUpdater $updater): void + { + $method = $this->methods['preflight'] ?? null; + if ($method instanceof Closure) { + $method->call($this); + } + } + + /** + * Runs right after installation. + */ + public function postflight(VersionUpdater $updater): void + { + $method = $this->methods['postflight'] ?? null; + if ($method instanceof Closure) { + $method->call($this); + } + } +} diff --git a/system/src/Grav/Installer/VersionUpdater.php b/system/src/Grav/Installer/VersionUpdater.php new file mode 100644 index 0000000..75a3b04 --- /dev/null +++ b/system/src/Grav/Installer/VersionUpdater.php @@ -0,0 +1,133 @@ +name = $name; + $this->path = $path; + $this->version = $version; + $this->versions = $versions; + + $this->loadUpdates(); + } + + /** + * Pre-installation method. + */ + public function preflight(): void + { + foreach ($this->updates as $revision => $update) { + $update->preflight($this); + } + } + + /** + * Install method. + */ + public function install(): void + { + $versions = $this->getVersions(); + $versions->updateVersion($this->name, $this->version); + $versions->save(); + } + + /** + * Post-installation method. + */ + public function postflight(): void + { + $versions = $this->getVersions(); + + foreach ($this->updates as $revision => $update) { + $update->postflight($this); + + $versions->setSchema($this->name, $revision); + $versions->save(); + } + } + + /** + * @return Versions + */ + public function getVersions(): Versions + { + return $this->versions; + } + + /** + * @param string|null $name + * @return string|null + */ + public function getExtensionVersion(string $name = null): ?string + { + return $this->versions->getVersion($name ?? $this->name); + } + + /** + * @param string|null $name + * @return string|null + */ + public function getExtensionSchema(string $name = null): ?string + { + return $this->versions->getSchema($name ?? $this->name); + } + + /** + * @param string|null $name + * @return array + */ + public function getExtensionHistory(string $name = null): array + { + return $this->versions->getHistory($name ?? $this->name); + } + + protected function loadUpdates(): void + { + $this->updates = []; + + $schema = $this->getExtensionSchema(); + $iterator = new DirectoryIterator($this->path); + foreach ($iterator as $item) { + if (!$item->isFile() || $item->getExtension() !== 'php') { + continue; + } + + $revision = $item->getBasename('.php'); + if (!$schema || version_compare($revision, $schema, '>')) { + $realPath = $item->getRealPath(); + if ($realPath) { + $this->updates[$revision] = new VersionUpdate($realPath, $this); + } + } + } + + uksort($this->updates, 'version_compare'); + } +} diff --git a/system/src/Grav/Installer/Versions.php b/system/src/Grav/Installer/Versions.php new file mode 100644 index 0000000..8b819ce --- /dev/null +++ b/system/src/Grav/Installer/Versions.php @@ -0,0 +1,329 @@ +updated) { + return false; + } + + file_put_contents($this->filename, Yaml::dump($this->items, 4, 2)); + + $this->updated = false; + + return true; + } + + /** + * @return array + */ + public function getAll(): array + { + return $this->items; + } + + /** + * @return array|null + */ + public function getGrav(): ?array + { + return $this->get('core/grav'); + } + + /** + * @return array + */ + public function getPlugins(): array + { + return $this->get('plugins', []); + } + + /** + * @param string $name + * @return array|null + */ + public function getPlugin(string $name): ?array + { + return $this->get("plugins/{$name}"); + } + + /** + * @return array + */ + public function getThemes(): array + { + return $this->get('themes', []); + } + + /** + * @param string $name + * @return array|null + */ + public function getTheme(string $name): ?array + { + return $this->get("themes/{$name}"); + } + + /** + * @param string $extension + * @return array|null + */ + public function getExtension(string $extension): ?array + { + return $this->get($extension); + } + + /** + * @param string $extension + * @param array|null $value + */ + public function setExtension(string $extension, ?array $value): void + { + if (null !== $value) { + $this->set($extension, $value); + } else { + $this->undef($extension); + } + } + + /** + * @param string $extension + * @return string|null + */ + public function getVersion(string $extension): ?string + { + $version = $this->get("{$extension}/version", null); + + return is_string($version) ? $version : null; + } + + /** + * @param string $extension + * @param string|null $version + */ + public function setVersion(string $extension, ?string $version): void + { + $this->updateHistory($extension, $version); + } + + /** + * NOTE: Updates also history. + * + * @param string $extension + * @param string|null $version + */ + public function updateVersion(string $extension, ?string $version): void + { + $this->set("{$extension}/version", $version); + $this->updateHistory($extension, $version); + } + + /** + * @param string $extension + * @return string|null + */ + public function getSchema(string $extension): ?string + { + $version = $this->get("{$extension}/schema", null); + + return is_string($version) ? $version : null; + } + + /** + * @param string $extension + * @param string|null $schema + */ + public function setSchema(string $extension, ?string $schema): void + { + if (null !== $schema) { + $this->set("{$extension}/schema", $schema); + } else { + $this->undef("{$extension}/schema"); + } + } + + /** + * @param string $extension + * @return array + */ + public function getHistory(string $extension): array + { + $name = "{$extension}/history"; + $history = $this->get($name, []); + + // Fix for broken Grav 1.6 history + if ($extension === 'grav') { + $history = $this->fixHistory($history); + } + + return $history; + } + + /** + * @param string $extension + * @param string|null $version + */ + public function updateHistory(string $extension, ?string $version): void + { + $name = "{$extension}/history"; + $history = $this->getHistory($extension); + $history[] = ['version' => $version, 'date' => gmdate('Y-m-d H:i:s')]; + $this->set($name, $history); + } + + /** + * Clears extension history. Useful when creating skeletons. + * + * @param string $extension + */ + public function removeHistory(string $extension): void + { + $this->undef("{$extension}/history"); + } + + /** + * @param array $history + * @return array + */ + private function fixHistory(array $history): array + { + if (isset($history['version'], $history['date'])) { + $fix = [['version' => $history['version'], 'date' => $history['date']]]; + unset($history['version'], $history['date']); + $history = array_merge($fix, $history); + } + + return $history; + } + + /** + * Get value by using dot notation for nested arrays/objects. + * + * @param string $name Slash separated path to the requested value. + * @param mixed $default Default value (or null). + * @return mixed Value. + */ + private function get(string $name, $default = null) + { + $path = explode('/', $name); + $current = $this->items; + + foreach ($path as $field) { + if (is_array($current) && isset($current[$field])) { + $current = $current[$field]; + } else { + return $default; + } + } + + return $current; + } + + /** + * Set value by using dot notation for nested arrays/objects. + * + * @param string $name Slash separated path to the requested value. + * @param mixed $value New value. + */ + private function set(string $name, $value): void + { + $path = explode('/', $name); + $current = &$this->items; + + foreach ($path as $field) { + // Handle arrays and scalars. + if (!is_array($current)) { + $current = [$field => []]; + } elseif (!isset($current[$field])) { + $current[$field] = []; + } + $current = &$current[$field]; + } + + $current = $value; + $this->updated = true; + } + + /** + * Unset value by using dot notation for nested arrays/objects. + * + * @param string $name Dot separated path to the requested value. + */ + private function undef(string $name): void + { + $path = $name !== '' ? explode('/', $name) : []; + if (!$path) { + return; + } + + $var = array_pop($path); + $current = &$this->items; + + foreach ($path as $field) { + if (!is_array($current) || !isset($current[$field])) { + return; + } + $current = &$current[$field]; + } + + unset($current[$var]); + $this->updated = true; + } + + private function __construct(string $filename) + { + $this->filename = $filename; + $content = is_file($filename) ? file_get_contents($filename) : null; + if (false === $content) { + throw new \RuntimeException('Versions file cannot be read'); + } + $this->items = $content ? Yaml::parse($content) : []; + } +} diff --git a/system/src/Grav/Installer/YamlUpdater.php b/system/src/Grav/Installer/YamlUpdater.php new file mode 100644 index 0000000..c43ba2c --- /dev/null +++ b/system/src/Grav/Installer/YamlUpdater.php @@ -0,0 +1,431 @@ +updated) { + return false; + } + + try { + if (!$this->isHandWritten()) { + $yaml = Yaml::dump($this->items, 5, 2); + } else { + $yaml = implode("\n", $this->lines); + + $items = Yaml::parse($yaml); + if ($items !== $this->items) { + throw new \RuntimeException('Failed saving the content'); + } + } + + file_put_contents($this->filename, $yaml); + + } catch (\Exception $e) { + throw new \RuntimeException('Failed to update ' . basename($this->filename) . ': ' . $e->getMessage()); + } + + return true; + } + + /** + * @return bool + */ + public function isHandWritten(): bool + { + return !empty($this->comments); + } + + /** + * @return array + */ + public function getComments(): array + { + $comments = []; + foreach ($this->lines as $i => $line) { + if ($this->isLineEmpty($line)) { + $comments[$i+1] = $line; + } elseif ($comment = $this->getInlineComment($line)) { + $comments[$i+1] = $comment; + } + } + + return $comments; + } + + /** + * @param string $variable + * @param mixed $value + */ + public function define(string $variable, $value): void + { + // If variable has already value, we're good. + if ($this->get($variable) !== null) { + return; + } + + // If one of the parents isn't array, we're good, too. + if (!$this->canDefine($variable)) { + return; + } + + $this->set($variable, $value); + if (!$this->isHandWritten()) { + return; + } + + $parts = explode('.', $variable); + + $lineNos = $this->findPath($this->lines, $parts); + $count = count($lineNos); + $last = array_key_last($lineNos); + + $value = explode("\n", trim(Yaml::dump([$last => $this->get(implode('.', array_keys($lineNos)))], max(0, 5-$count), 2))); + $currentLine = array_pop($lineNos) ?: 0; + $parentLine = array_pop($lineNos); + + if ($parentLine !== null) { + $c = $this->getLineIndentation($this->lines[$parentLine] ?? ''); + $n = $this->getLineIndentation($this->lines[$parentLine+1] ?? $this->lines[$parentLine] ?? ''); + $indent = $n > $c ? $n : $c + 2; + } else { + $indent = 0; + array_unshift($value, ''); + } + $spaces = str_repeat(' ', $indent); + foreach ($value as &$line) { + $line = $spaces . $line; + } + unset($line); + + array_splice($this->lines, abs($currentLine)+1, 0, $value); + } + + public function undefine(string $variable): void + { + // If variable does not have value, we're good. + if ($this->get($variable) === null) { + return; + } + + // If one of the parents isn't array, we're good, too. + if (!$this->canDefine($variable)) { + return; + } + + $this->undef($variable); + if (!$this->isHandWritten()) { + return; + } + + // TODO: support also removing property from handwritten configuration file. + } + + private function __construct(string $filename) + { + $content = is_file($filename) ? (string)file_get_contents($filename) : ''; + $content = rtrim(str_replace(["\r\n", "\r"], "\n", $content)); + + $this->filename = $filename; + $this->lines = explode("\n", $content); + $this->comments = $this->getComments(); + $this->items = $content ? Yaml::parse($content) : []; + } + + /** + * Return array of offsets for the parent nodes. Negative value means position, but not found. + * + * @param array $lines + * @param array $parts + * @return int[] + */ + private function findPath(array $lines, array $parts) + { + $test = true; + $indent = -1; + $current = array_shift($parts); + + $j = 1; + $found = []; + $space = ''; + foreach ($lines as $i => $line) { + if ($this->isLineEmpty($line)) { + if ($this->isLineComment($line) && $this->getLineIndentation($line) > $indent) { + $j = $i; + } + continue; + } + + if ($test === true) { + $test = false; + $spaces = strlen($line) - strlen(ltrim($line, ' ')); + if ($spaces <= $indent) { + $found[$current] = -$j; + + return $found; + } + + $indent = $spaces; + $space = $indent ? str_repeat(' ', $indent) : ''; + } + + + if (0 === \strncmp($line, $space, strlen($space))) { + $pattern = "/^{$space}(['\"]?){$current}\\1\:/"; + + if (preg_match($pattern, $line)) { + $found[$current] = $i; + $current = array_shift($parts); + if ($current === null) { + return $found; + } + $test = true; + } + } else { + $found[$current] = -$j; + + return $found; + } + + $j = $i; + } + + $found[$current] = -$j; + + return $found; + } + + /** + * Returns true if the current line is blank or if it is a comment line. + * + * @param string $line Contents of the line + * @return bool Returns true if the current line is empty or if it is a comment line, false otherwise + */ + private function isLineEmpty(string $line): bool + { + return $this->isLineBlank($line) || $this->isLineComment($line); + } + + /** + * Returns true if the current line is blank. + * + * @param string $line Contents of the line + * @return bool Returns true if the current line is blank, false otherwise + */ + private function isLineBlank(string $line): bool + { + return '' === trim($line, ' '); + } + + /** + * Returns true if the current line is a comment line. + * + * @param string $line Contents of the line + * @return bool Returns true if the current line is a comment line, false otherwise + */ + private function isLineComment(string $line): bool + { + //checking explicitly the first char of the trim is faster than loops or strpos + $ltrimmedLine = ltrim($line, ' '); + + return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0]; + } + + /** + * @param string $line + * @return bool + */ + private function isInlineComment(string $line): bool + { + return $this->getInlineComment($line) !== null; + } + + /** + * @param string $line + * @return string|null + */ + private function getInlineComment(string $line): ?string + { + $pos = strpos($line, ' #'); + if (false === $pos) { + return null; + } + + $parts = explode(' #', $line); + $part = ''; + while ($part .= array_shift($parts)) { + // Remove quoted values. + $part = preg_replace('/(([\'"])[^\2]*\2)/', '', $part); + assert(null !== $part); + $part = preg_split('/[\'"]/', $part, 2); + assert(false !== $part); + if (!isset($part[1])) { + $part = $part[0]; + array_unshift($parts, str_repeat(' ', strlen($part) - strlen(trim($part, ' ')))); + break; + } + $part = $part[1]; + } + + + return implode(' #', $parts); + } + + /** + * Returns the current line indentation. + * + * @param string $line + * @return int The current line indentation + */ + private function getLineIndentation(string $line): int + { + return \strlen($line) - \strlen(ltrim($line, ' ')); + } + + /** + * Get value by using dot notation for nested arrays/objects. + * + * @param string $name Dot separated path to the requested value. + * @param mixed $default Default value (or null). + * @return mixed Value. + */ + private function get(string $name, $default = null) + { + $path = explode('.', $name); + $current = $this->items; + + foreach ($path as $field) { + if (is_array($current) && isset($current[$field])) { + $current = $current[$field]; + } else { + return $default; + } + } + + return $current; + } + + /** + * Set value by using dot notation for nested arrays/objects. + * + * @param string $name Dot separated path to the requested value. + * @param mixed $value New value. + */ + private function set(string $name, $value): void + { + $path = explode('.', $name); + $current = &$this->items; + + foreach ($path as $field) { + // Handle arrays and scalars. + if (!is_array($current)) { + $current = [$field => []]; + } elseif (!isset($current[$field])) { + $current[$field] = []; + } + $current = &$current[$field]; + } + + $current = $value; + $this->updated = true; + } + + /** + * Unset value by using dot notation for nested arrays/objects. + * + * @param string $name Dot separated path to the requested value. + */ + private function undef(string $name): void + { + $path = $name !== '' ? explode('.', $name) : []; + if (!$path) { + return; + } + + $var = array_pop($path); + $current = &$this->items; + + foreach ($path as $field) { + if (!is_array($current) || !isset($current[$field])) { + return; + } + $current = &$current[$field]; + } + + unset($current[$var]); + $this->updated = true; + } + + /** + * Get value by using dot notation for nested arrays/objects. + * + * @param string $name Dot separated path to the requested value. + * @return bool + */ + private function canDefine(string $name): bool + { + $path = explode('.', $name); + $current = $this->items; + + foreach ($path as $field) { + if (is_array($current)) { + if (!isset($current[$field])) { + return true; + } + $current = $current[$field]; + } else { + return false; + } + } + + return true; + } +} diff --git a/system/src/Grav/Installer/updates/1.7.0_2020-11-20_1.php b/system/src/Grav/Installer/updates/1.7.0_2020-11-20_1.php new file mode 100644 index 0000000..6120665 --- /dev/null +++ b/system/src/Grav/Installer/updates/1.7.0_2020-11-20_1.php @@ -0,0 +1,24 @@ + null, + 'postflight' => + function () { + /** @var VersionUpdate $this */ + try { + // Keep old defaults for backwards compatibility. + $yaml = YamlUpdater::instance(GRAV_ROOT . '/user/config/system.yaml'); + $yaml->define('twig.autoescape', false); + $yaml->define('strict_mode.yaml_compat', true); + $yaml->define('strict_mode.twig_compat', true); + $yaml->define('strict_mode.blueprint_compat', true); + $yaml->save(); + } catch (\Exception $e) { + throw new InstallException('Could not update system configuration to maintain backwards compatibility', $e); + } + } +]; diff --git a/system/src/Twig/DeferredExtension/DeferredBlockNode.php b/system/src/Twig/DeferredExtension/DeferredBlockNode.php new file mode 100755 index 0000000..6ae974f --- /dev/null +++ b/system/src/Twig/DeferredExtension/DeferredBlockNode.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Twig\DeferredExtension; + +use Twig\Compiler; +use Twig\Node\BlockNode; + +final class DeferredBlockNode extends BlockNode +{ + public function compile(Compiler $compiler) : void + { + $name = $this->getAttribute('name'); + + $compiler + ->write("public function block_$name(\$context, array \$blocks = [])\n", "{\n") + ->indent() + ->write("\$this->deferred->defer(\$this, '$name');\n") + ->outdent() + ->write("}\n\n") + ; + + $compiler + ->addDebugInfo($this) + ->write("public function block_{$name}_deferred(\$context, array \$blocks = [])\n", "{\n") + ->indent() + ->subcompile($this->getNode('body')) + ->write("\$this->deferred->resolve(\$this, \$context, \$blocks);\n") + ->outdent() + ->write("}\n\n") + ; + } +} diff --git a/system/src/Twig/DeferredExtension/DeferredDeclareNode.php b/system/src/Twig/DeferredExtension/DeferredDeclareNode.php new file mode 100644 index 0000000..ba05121 --- /dev/null +++ b/system/src/Twig/DeferredExtension/DeferredDeclareNode.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Twig\DeferredExtension; + +use Twig\Compiler; +use Twig\Node\Node; + +final class DeferredDeclareNode extends Node +{ + public function compile(Compiler $compiler) : void + { + $compiler + ->write("private \$deferred;\n") + ; + } +} \ No newline at end of file diff --git a/system/src/Twig/DeferredExtension/DeferredExtension.php b/system/src/Twig/DeferredExtension/DeferredExtension.php new file mode 100644 index 0000000..f27c2a3 --- /dev/null +++ b/system/src/Twig/DeferredExtension/DeferredExtension.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Twig\DeferredExtension; + +use Twig\Environment; +use Twig\Extension\AbstractExtension; +use Twig\Template; + +final class DeferredExtension extends AbstractExtension +{ + private $blocks = []; + + public function getTokenParsers() : array + { + return [new DeferredTokenParser()]; + } + + public function getNodeVisitors() : array + { + if (Environment::VERSION_ID < 20000) { + // Twig 1.x support + return [new DeferredNodeVisitorCompat()]; + } + + return [new DeferredNodeVisitor()]; + } + + public function defer(Template $template, string $blockName) : void + { + $templateName = $template->getTemplateName(); + $this->blocks[$templateName][] = $blockName; + $index = \count($this->blocks[$templateName]) - 1; + + \ob_start(function (string $buffer) use ($index, $templateName) { + unset($this->blocks[$templateName][$index]); + + return $buffer; + }); + } + + public function resolve(Template $template, array $context, array $blocks) : void + { + $templateName = $template->getTemplateName(); + if (empty($this->blocks[$templateName])) { + return; + } + + while ($blockName = \array_pop($this->blocks[$templateName])) { + $buffer = \ob_get_clean(); + + $blocks[$blockName] = [$template, 'block_'.$blockName.'_deferred']; + $template->displayBlock($blockName, $context, $blocks); + + echo $buffer; + } + + if ($parent = $template->getParent($context)) { + $this->resolve($parent, $context, $blocks); + } + } +} diff --git a/system/src/Twig/DeferredExtension/DeferredInitializeNode.php b/system/src/Twig/DeferredExtension/DeferredInitializeNode.php new file mode 100644 index 0000000..0653f5c --- /dev/null +++ b/system/src/Twig/DeferredExtension/DeferredInitializeNode.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Twig\DeferredExtension; + +use Twig\Compiler; +use Twig\Node\Node; + +final class DeferredInitializeNode extends Node +{ + public function compile(Compiler $compiler) : void + { + $compiler + ->write("\$this->deferred = \$this->env->getExtension('".DeferredExtension::class."');\n") + ; + } +} diff --git a/system/src/Twig/DeferredExtension/DeferredNode.php b/system/src/Twig/DeferredExtension/DeferredNode.php new file mode 100755 index 0000000..2ac73bd --- /dev/null +++ b/system/src/Twig/DeferredExtension/DeferredNode.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Twig\DeferredExtension; + +use Twig\Compiler; +use Twig\Node\Node; + +final class DeferredNode extends Node +{ + public function compile(Compiler $compiler) : void + { + $compiler + ->write("\$this->deferred->resolve(\$this, \$context, \$blocks);\n") + ; + } +} diff --git a/system/src/Twig/DeferredExtension/DeferredNodeVisitor.php b/system/src/Twig/DeferredExtension/DeferredNodeVisitor.php new file mode 100644 index 0000000..6f61487 --- /dev/null +++ b/system/src/Twig/DeferredExtension/DeferredNodeVisitor.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Twig\DeferredExtension; + +use Twig\Environment; +use Twig\Node\ModuleNode; +use Twig\Node\Node; +use Twig\NodeVisitor\NodeVisitorInterface; + +final class DeferredNodeVisitor implements NodeVisitorInterface +{ + private $hasDeferred = false; + + public function enterNode(Node $node, Environment $env) : Node + { + if (!$this->hasDeferred && $node instanceof DeferredBlockNode) { + $this->hasDeferred = true; + } + + return $node; + } + + public function leaveNode(Node $node, Environment $env) : ?Node + { + if ($this->hasDeferred && $node instanceof ModuleNode) { + $node->getNode('constructor_end')->setNode('deferred_initialize', new DeferredInitializeNode()); + $node->getNode('display_end')->setNode('deferred_resolve', new DeferredResolveNode()); + $node->getNode('class_end')->setNode('deferred_declare', new DeferredDeclareNode()); + $this->hasDeferred = false; + } + + return $node; + } + + public function getPriority() : int + { + return 0; + } +} diff --git a/system/src/Twig/DeferredExtension/DeferredNodeVisitorCompat.php b/system/src/Twig/DeferredExtension/DeferredNodeVisitorCompat.php new file mode 100644 index 0000000..aa61b72 --- /dev/null +++ b/system/src/Twig/DeferredExtension/DeferredNodeVisitorCompat.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Twig\DeferredExtension; + +use Twig\Environment; +use Twig\Node\ModuleNode; +use Twig\Node\Node; +use Twig\NodeVisitor\NodeVisitorInterface; + +final class DeferredNodeVisitorCompat implements NodeVisitorInterface +{ + private $hasDeferred = false; + + /** + * @param \Twig_NodeInterface $node + * @param Environment $env + * @return Node + */ + public function enterNode(\Twig_NodeInterface $node, Environment $env): Node + { + if (!$this->hasDeferred && $node instanceof DeferredBlockNode) { + $this->hasDeferred = true; + } + + \assert($node instanceof Node); + + return $node; + } + + /** + * @param \Twig_NodeInterface $node + * @param Environment $env + * @return Node|null + */ + public function leaveNode(\Twig_NodeInterface $node, Environment $env): ?Node + { + if ($this->hasDeferred && $node instanceof ModuleNode) { + $node->getNode('constructor_end')->setNode('deferred_initialize', new DeferredInitializeNode()); + $node->getNode('display_end')->setNode('deferred_resolve', new DeferredResolveNode()); + $node->getNode('class_end')->setNode('deferred_declare', new DeferredDeclareNode()); + $this->hasDeferred = false; + } + + \assert($node instanceof Node); + + return $node; + } + + /** + * @return int + */ + public function getPriority() : int + { + return 0; + } +} diff --git a/system/src/Twig/DeferredExtension/DeferredResolveNode.php b/system/src/Twig/DeferredExtension/DeferredResolveNode.php new file mode 100644 index 0000000..72e0e29 --- /dev/null +++ b/system/src/Twig/DeferredExtension/DeferredResolveNode.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Twig\DeferredExtension; + +use Twig\Compiler; +use Twig\Node\Node; + +final class DeferredResolveNode extends Node +{ + public function compile(Compiler $compiler) : void + { + $compiler + ->write("\$this->deferred->resolve(\$this, \$context, \$blocks);\n") + ; + } +} diff --git a/system/src/Twig/DeferredExtension/DeferredTokenParser.php b/system/src/Twig/DeferredExtension/DeferredTokenParser.php new file mode 100644 index 0000000..1870ae0 --- /dev/null +++ b/system/src/Twig/DeferredExtension/DeferredTokenParser.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Twig\DeferredExtension; + +use Twig\Node\BlockNode; +use Twig\Node\Node; +use Twig\Parser; +use Twig\Token; +use Twig\TokenParser\AbstractTokenParser; +use Twig\TokenParser\BlockTokenParser; + +final class DeferredTokenParser extends AbstractTokenParser +{ + private $blockTokenParser; + + public function setParser(Parser $parser) : void + { + parent::setParser($parser); + + $this->blockTokenParser = new BlockTokenParser(); + $this->blockTokenParser->setParser($parser); + } + + public function parse(Token $token) : Node + { + $stream = $this->parser->getStream(); + $nameToken = $stream->next(); + $deferredToken = $stream->nextIf(Token::NAME_TYPE, 'deferred'); + $stream->injectTokens([$nameToken]); + + $node = $this->blockTokenParser->parse($token); + + if ($deferredToken) { + $this->replaceBlockNode($nameToken->getValue()); + } + + return $node; + } + + public function getTag() : string + { + return 'block'; + } + + private function replaceBlockNode(string $name) : void + { + $block = $this->parser->getBlock($name)->getNode('0'); + $this->parser->setBlock($name, $this->createDeferredBlockNode($block)); + } + + private function createDeferredBlockNode(BlockNode $block) : DeferredBlockNode + { + $name = $block->getAttribute('name'); + $deferredBlock = new DeferredBlockNode($name, new Node([]), $block->getTemplateLine()); + + foreach ($block as $nodeName => $node) { + $deferredBlock->setNode($nodeName, $node); + } + + if ($sourceContext = $block->getSourceContext()) { + $deferredBlock->setSourceContext($sourceContext); + } + + return $deferredBlock; + } +} diff --git a/system/templates/default.html.twig b/system/templates/default.html.twig new file mode 100644 index 0000000..f18206b --- /dev/null +++ b/system/templates/default.html.twig @@ -0,0 +1,4 @@ +{# Default output if no theme #} +

    ERROR: {{ page.template() ~'.'~ page.templateFormat() ~".twig" }} template not found for page: {{ page.route() }}

    +

    {{ page.title() }}

    +{{ page.content()|raw }} diff --git a/system/templates/external.html.twig b/system/templates/external.html.twig new file mode 100644 index 0000000..3fa3508 --- /dev/null +++ b/system/templates/external.html.twig @@ -0,0 +1 @@ +{# Default external template #} diff --git a/system/templates/flex/404.html.twig b/system/templates/flex/404.html.twig new file mode 100644 index 0000000..adf4f65 --- /dev/null +++ b/system/templates/flex/404.html.twig @@ -0,0 +1,4 @@ +{% set item = collection ?? object %} +{% set type = collection ? 'collection' : 'object' %} + +ERROR: Layout '{{ layout }}' for flex {{ type }} '{{ item.flexType() }}' was not found. \ No newline at end of file diff --git a/system/templates/flex/_default/collection/debug.html.twig b/system/templates/flex/_default/collection/debug.html.twig new file mode 100644 index 0000000..5a37835 --- /dev/null +++ b/system/templates/flex/_default/collection/debug.html.twig @@ -0,0 +1,5 @@ +

    {{ directory.getTitle() }} debug dump

    + +{% for object in collection %} + {% render object layout: layout %} +{% endfor %} diff --git a/system/templates/flex/_default/object/debug.html.twig b/system/templates/flex/_default/object/debug.html.twig new file mode 100644 index 0000000..dc961cd --- /dev/null +++ b/system/templates/flex/_default/object/debug.html.twig @@ -0,0 +1,4 @@ +
    +

    {{ object.key }}

    +
    {{ object.jsonSerialize()|yaml_encode }}
    +
    \ No newline at end of file diff --git a/system/templates/modular/default.html.twig b/system/templates/modular/default.html.twig new file mode 100644 index 0000000..f18206b --- /dev/null +++ b/system/templates/modular/default.html.twig @@ -0,0 +1,4 @@ +{# Default output if no theme #} +

    ERROR: {{ page.template() ~'.'~ page.templateFormat() ~".twig" }} template not found for page: {{ page.route() }}

    +

    {{ page.title() }}

    +{{ page.content()|raw }} diff --git a/system/templates/partials/messages.html.twig b/system/templates/partials/messages.html.twig new file mode 100644 index 0000000..261b3fc --- /dev/null +++ b/system/templates/partials/messages.html.twig @@ -0,0 +1,14 @@ +{% set status_mapping = {'info':'green', 'error': 'red', 'warning': 'yellow'} %} + +{% if grav.messages.all %} +
    + {% for message in grav.messages.fetch %} + + {% set scope = message.scope|e %} + {% set color = status_mapping[scope] %} + +

    {{ message.message|raw }}

    + + {% endfor %} +
    +{% endif %} diff --git a/system/templates/partials/metadata.html.twig b/system/templates/partials/metadata.html.twig new file mode 100644 index 0000000..fcf1217 --- /dev/null +++ b/system/templates/partials/metadata.html.twig @@ -0,0 +1,3 @@ +{% for meta in page.metadata %} + +{% endfor %} diff --git a/tests/_bootstrap.php b/tests/_bootstrap.php new file mode 100644 index 0000000..618781d --- /dev/null +++ b/tests/_bootstrap.php @@ -0,0 +1,35 @@ +init(); + + // This must be set first before the other init + $grav['config']->set('system.languages.supported', ['en', 'fr', 'vi']); + $grav['config']->set('system.languages.default_lang', 'en'); + + foreach (array_keys($grav['setup']->getStreams()) as $stream) { + @stream_wrapper_unregister($stream); + } + + $grav['streams']; + + $grav['uri']->init(); + $grav['debugger']->init(); + $grav['assets']->init(); + + $grav['config']->set('system.cache.enabled', false); + $grav['locator']->addPath('tests', '', 'tests', false); + + return $grav; +}; + +Fixtures::add('grav', $grav); diff --git a/tests/_support/AcceptanceTester.php b/tests/_support/AcceptanceTester.php new file mode 100644 index 0000000..4c7dcbb --- /dev/null +++ b/tests/_support/AcceptanceTester.php @@ -0,0 +1,26 @@ +t!fmROooqL-Tz8o|Tx<=pzP32bY^zA}n1m`*SZ zayH~~sh*$2f56|-lb6+ai;Tk!uM0i7lF_H0{5Zi`GEH@D+5JM9lSj1nKe)T&;m-;` z_diSd=LVjNxEHYH)e#|P({1W0Z@V{d{K+CJxk<%)f5U2*^1@Eu4L`D?f>!k1m^N>z z$H%+^=cVf}u-|r`eeuO5&XA|aU$*!&muD*Wsr@eOopdYsS7GFfT{%^Z_bbv2jV|)7 zpBi*{E6Yu+0_pvPdZ<#k#~Xw_4`vsh3VLn7h~tUHZ`TtSwkz_?3jAU7@a?opiQms8cudkC$rRsg7umFT zp>6NVpdI;LvqNOg#<43ZIh=ofz(-?G#k;)AyK*nEsWc}4E&N*MwLid{oujB&wp)>r zf#C)a2Y53wi83RC8d;8ufd>}w4oezAOyq#)fCqelH!B-RmJtYDfOIC%Tm}XJO6sxU literal 0 HcmV?d00001 diff --git a/tests/fake/nested-site/user/pages/01.item1/home-cache-image.jpg b/tests/fake/nested-site/user/pages/01.item1/home-cache-image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3170ac497409e7c7923d25e3eae4b9e4a920b211 GIT binary patch literal 156699 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�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

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

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

    xa_u8c)hsQoXBakm44Rj@Hu~WCdpmw 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 literal 0 HcmV?d00001 diff --git a/tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg b/tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3170ac497409e7c7923d25e3eae4b9e4a920b211 GIT binary patch literal 156699 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�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

    xa_u8c)hsQoXBakm44Rj@Hu~WCdpmw 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: animalgrav = Fixtures::get('grav'); + $this->directInstallCommand = new DirectInstallCommand(); + } +} + +/** + * Why this test file is empty + * + * Wasn't able to call a symfony\console. Kept having $output problem. + * symfony console \NullOutput didn't cut it. + * + * We would also need to Mock tests since downloading packages would + * make tests slow and unreliable. But it's not worth the time ATM. + * + * Look at Gpm/InstallCommandTest.php + * + * For the full story: https://git.io/vSlI3 + */ diff --git a/tests/functional/_bootstrap.php b/tests/functional/_bootstrap.php new file mode 100644 index 0000000..8a88555 --- /dev/null +++ b/tests/functional/_bootstrap.php @@ -0,0 +1,2 @@ +getName() === 'findResource'; + } + + /** + * @param MethodReflection $methodReflection + * @param MethodCall $methodCall + * @param Scope $scope + * @return Type + */ + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + $first = $methodCall->getArgs()[2] ?? false; + if ($first) { + return new StringType(); + } + + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } +} diff --git a/tests/phpstan/extension.neon b/tests/phpstan/extension.neon new file mode 100644 index 0000000..ef44d0b --- /dev/null +++ b/tests/phpstan/extension.neon @@ -0,0 +1,5 @@ +services: + - + class: PHPStan\Toolbox\UniformResourceLocatorExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension diff --git a/tests/phpstan/phpstan-bootstrap.php b/tests/phpstan/phpstan-bootstrap.php new file mode 100644 index 0000000..b145fa1 --- /dev/null +++ b/tests/phpstan/phpstan-bootstrap.php @@ -0,0 +1,7 @@ + $autoload]); +$grav->setup('tests'); +$grav['config']->init(); + +// Find all plugins in Grav installation and autoload their classes. + +/** @var UniformResourceLocator $locator */ +$locator = Grav::instance()['locator']; +$iterator = $locator->getIterator('plugins://'); +/** @var DirectoryIterator $directory */ +foreach ($iterator as $directory) { + if (!$directory->isDir()) { + continue; + } + $plugin = $directory->getBasename(); + $file = $directory->getPathname() . '/' . $plugin . '.php'; + $classloader = null; + if (file_exists($file)) { + require_once $file; + + $pluginClass = "\\Grav\\Plugin\\{$plugin}Plugin"; + + if (is_subclass_of($pluginClass, Plugin::class, true)) { + $class = new $pluginClass($plugin, $grav); + if (is_callable([$class, 'autoload'])) { + $classloader = $class->autoload(); + } + } + } + if (null === $classloader) { + $autoloader = $directory->getPathname() . '/vendor/autoload.php'; + if (file_exists($autoloader)) { + require $autoloader; + } + } +} + +define('GANTRY_DEBUGGER', true); +define('GANTRY5_DEBUG', true); +define('GANTRY5_PLATFORM', 'grav'); +define('GANTRY5_ROOT', GRAV_ROOT); +define('GANTRY5_VERSION', '@version@'); +define('GANTRY5_VERSION_DATE', '@versiondate@'); +define('GANTRYADMIN_PATH', ''); diff --git a/tests/phpstan/plugins.neon b/tests/phpstan/plugins.neon new file mode 100644 index 0000000..82570cc --- /dev/null +++ b/tests/phpstan/plugins.neon @@ -0,0 +1,70 @@ +includes: + #- '../../vendor/phpstan/phpstan-strict-rules/rules.neon' + - '../../vendor/phpstan/phpstan-deprecation-rules/rules.neon' + - 'extension.neon' +parameters: + fileExtensions: + - php + excludePaths: + - %currentWorkingDirectory%/user/plugins/*/vendor/* + - %currentWorkingDirectory%/user/plugins/*/tests/* + - %currentWorkingDirectory%/user/plugins/gantry5/src/platforms + - %currentWorkingDirectory%/user/plugins/gantry5/src/classes/Gantry/Framework/Services/ErrorServiceProvider.php + # Ignore vendor dev dependencies and tests + - */vendor/*/*/tests + - */vendor/behat + - */vendor/codeception + - */vendor/phpstan + - */vendor/phpunit + - */vendor/phpspec + - */vendor/phpdocumentor + - */vendor/sebastian + - */vendor/theseer + - */vendor/webmozart + bootstrapFiles: + - plugins-bootstrap.php + inferPrivatePropertyTypeFromConstructor: true + reportUnmatchedIgnoredErrors: false + + # These checks are new in phpstan 1, ignore them for now. + checkMissingIterableValueType: false + checkGenericClassInNonGenericObjectType: false + + universalObjectCratesClasses: + - Grav\Common\Config\Config + - Grav\Common\Config\Languages + - Grav\Common\Config\Setup + - Grav\Common\Data\Data + - Grav\Common\GPM\Common\Package + - Grav\Common\GPM\Local\Package + - Grav\Common\GPM\Remote\Package + - Grav\Common\Page\Header + - Grav\Common\Session + - Gantry\Component\Config\Config + dynamicConstantNames: + - GRAV_CLI + - GANTRY_DEBUGGER + - GANTRY5_DEBUG + - GANTRY5_VERSION + - GANTRY5_VERSION_DATE + - GANTRY5_PLATFORM + - GANTRY5_ROOT + ignoreErrors: + # New in phpstan 1, ignore them for now. + - '#Unsafe usage of new static\(\)#' + - '#Cannot instantiate interface Grav\\Framework\\#' + + # PSR-16 Exception interfaces do not extend \Throwable + - '#PHPDoc tag \@throws with type (.*|)?Psr\\SimpleCache\\(CacheException|InvalidArgumentException)(|.*)? is not subtype of Throwable#' + + - '#Access to an undefined property RocketTheme\\Toolbox\\Event\\Event::#' + - '#Instantiation of deprecated class RocketTheme\\Toolbox\\Event\\Event#' + - '#extends deprecated class RocketTheme\\Toolbox\\Event\\Event#' + - '#implements deprecated interface RocketTheme\\Toolbox\\Event\\EventSubscriberInterface#' + - '#Call to method __construct\(\) of deprecated class RocketTheme\\Toolbox\\Event\\Event#' + - '#Call to deprecated method (stopPropagation|isPropagationStopped)\(\) of class Symfony\\Component\\EventDispatcher\\Event#' + - '#Call to an undefined method Grav\\Plugin\\ApartmentData\\Application\\Application::#' + - '#Parameter \#1 \$lineNumberStyle of method ScssPhp\\ScssPhp\\Compiler::setLineNumberStyle\(\) expects string, int given#' + + # Deprecated event class + - '#has typehint with deprecated class RocketTheme\\Toolbox\\Event\\Event#' diff --git a/tests/unit.suite.yml b/tests/unit.suite.yml new file mode 100644 index 0000000..02dc9b1 --- /dev/null +++ b/tests/unit.suite.yml @@ -0,0 +1,9 @@ +# Codeception Test Suite Configuration +# +# Suite for unit (internal) tests. + +class_name: UnitTester +modules: + enabled: + - Asserts + - \Helper\Unit \ No newline at end of file diff --git a/tests/unit/Grav/Common/AssetsTest.php b/tests/unit/Grav/Common/AssetsTest.php new file mode 100644 index 0000000..57539a6 --- /dev/null +++ b/tests/unit/Grav/Common/AssetsTest.php @@ -0,0 +1,847 @@ +grav = $grav(); + $this->assets = $this->grav['assets']; + } + + protected function _after(): void + { + } + + public function testAddingAssets(): void + { + //test add() + $this->assets->add('test.css'); + + $css = $this->assets->css(); + self::assertSame('' . PHP_EOL, $css); + + $array = $this->assets->getCss(); + + /** @var Assets\BaseAsset $item */ + $item = reset($array); + $actual = json_encode($item); + $expected = ' + { + "type":"css", + "elements":{ + "asset":"\/test.css", + "asset_type":"css", + "order":0, + "group":"head", + "position":"pipeline", + "priority":10, + "attributes":{ + "type":"text\/css", + "rel":"stylesheet" + }, + "modified":false, + "query":"" + } + }'; + self::assertJsonStringEqualsJsonString($expected, $actual); + + $this->assets->add('test.js'); + $js = $this->assets->js(); + self::assertSame('' . PHP_EOL, $js); + + $array = $this->assets->getJs(); + + /** @var Assets\BaseAsset $item */ + $item = reset($array); + $actual = json_encode($item); + $expected = ' + { + "type":"js", + "elements":{ + "asset":"\/test.js", + "asset_type":"js", + "order":0, + "group":"head", + "position":"pipeline", + "priority":10, + "attributes":[ + + ], + "modified":false, + "query":"" + } + }'; + self::assertJsonStringEqualsJsonString($expected, $actual); + + //test addCss(). Test adding asset to a separate group + $this->assets->reset(); + $this->assets->addCSS('test.css'); + $css = $this->assets->css(); + self::assertSame('' . PHP_EOL, $css); + + $array = $this->assets->getCss(); + /** @var Assets\BaseAsset $item */ + $item = reset($array); + $actual = json_encode($item); + $expected = ' + { + "type":"css", + "elements":{ + "asset":"\/test.css", + "asset_type":"css", + "order":0, + "group":"head", + "position":"pipeline", + "priority":10, + "attributes":{ + "type":"text\/css", + "rel":"stylesheet" + }, + "modified":false, + "query":"" + } + }'; + self::assertJsonStringEqualsJsonString($expected, $actual); + + //test addCss(). Testing with remote URL + $this->assets->reset(); + $this->assets->addCSS('http://www.somesite.com/test.css'); + $css = $this->assets->css(); + self::assertSame('' . PHP_EOL, $css); + + $array = $this->assets->getCss(); + /** @var Assets\BaseAsset $item */ + $item = reset($array); + $actual = json_encode($item); + $expected = ' + { + "type":"css", + "elements":{ + "asset":"http:\/\/www.somesite.com\/test.css", + "asset_type":"css", + "order":0, + "group":"head", + "position":"pipeline", + "priority":10, + "attributes":{ + "type":"text\/css", + "rel":"stylesheet" + }, + "query":"" + } + }'; + self::assertJsonStringEqualsJsonString($expected, $actual); + + //test addCss() adding asset to a separate group, and with an alternate rel attribute + $this->assets->reset(); + $this->assets->addCSS('test.css', ['group' => 'alternate', 'rel' => 'alternate']); + $css = $this->assets->css('alternate'); + self::assertSame('' . PHP_EOL, $css); + + //test addJs() + $this->assets->reset(); + $this->assets->addJs('test.js'); + $js = $this->assets->js(); + self::assertSame('' . PHP_EOL, $js); + + $array = $this->assets->getJs(); + /** @var Assets\BaseAsset $item */ + $item = reset($array); + $actual = json_encode($item); + $expected = ' + { + "type":"js", + "elements":{ + "asset":"\/test.js", + "asset_type":"js", + "order":0, + "group":"head", + "position":"pipeline", + "priority":10, + "attributes":[], + "modified":false, + "query":"" + } + }'; + self::assertJsonStringEqualsJsonString($expected, $actual); + + //Test CSS Groups + $this->assets->reset(); + $this->assets->addCSS('test.css', ['group' => 'footer']); + $css = $this->assets->css(); + self::assertEmpty($css); + $css = $this->assets->css('footer'); + self::assertSame('' . PHP_EOL, $css); + + $array = $this->assets->getCss(); + /** @var Assets\BaseAsset $item */ + $item = reset($array); + $actual = json_encode($item); + $expected = ' + { + "type": "css", + "elements": { + "asset": "/test.css", + "asset_type": "css", + "order": 0, + "group": "footer", + "position": "pipeline", + "priority": 10, + "attributes": { + "type": "text/css", + "rel": "stylesheet" + }, + "modified": false, + "query": "" + } + } + '; + self::assertJsonStringEqualsJsonString($expected, $actual); + + //Test JS Groups + $this->assets->reset(); + $this->assets->addJs('test.js', ['group' => 'footer']); + $js = $this->assets->js(); + self::assertEmpty($js); + $js = $this->assets->js('footer'); + self::assertSame('' . PHP_EOL, $js); + + $array = $this->assets->getJs(); + /** @var Assets\BaseAsset $item */ + $item = reset($array); + $actual = json_encode($item); + $expected = ' + { + "type": "js", + "elements": { + "asset": "/test.js", + "asset_type": "js", + "order": 0, + "group": "footer", + "position": "pipeline", + "priority": 10, + "attributes": [], + "modified": false, + "query": "" + } + }'; + self::assertJsonStringEqualsJsonString($expected, $actual); + + //Test async / defer + $this->assets->reset(); + $this->assets->addJs('test.js', ['loading' => 'async']); + $js = $this->assets->js(); + self::assertSame('' . PHP_EOL, $js); + + $array = $this->assets->getJs(); + /** @var Assets\BaseAsset $item */ + $item = reset($array); + $actual = json_encode($item); + $expected = ' + { + "type": "js", + "elements": { + "asset": "/test.js", + "asset_type": "js", + "order": 0, + "group": "head", + "position": "pipeline", + "priority": 10, + "attributes": { + "loading": "async" + }, + "modified": false, + "query": "" + } + }'; + self::assertJsonStringEqualsJsonString($expected, $actual); + + $this->assets->reset(); + $this->assets->addJs('test.js', ['loading' => 'defer']); + $js = $this->assets->js(); + self::assertSame('' . PHP_EOL, $js); + + $array = $this->assets->getJs(); + /** @var Assets\BaseAsset $item */ + $item = reset($array); + $actual = json_encode($item); + $expected = ' + { + "type": "js", + "elements": { + "asset": "/test.js", + "asset_type": "js", + "order": 0, + "group": "head", + "position": "pipeline", + "priority": 10, + "attributes": { + "loading": "defer" + }, + "modified": false, + "query": "" + } + }'; + self::assertJsonStringEqualsJsonString($expected, $actual); + + //Test inline + $this->assets->reset(); + $this->assets->setJsPipeline(true); + $this->assets->addJs('/system/assets/jquery/jquery-3.x.min.js'); + $js = $this->assets->js('head', ['loading' => 'inline']); + self::assertStringContainsString('"jquery",[],function()', $js); + + $this->assets->reset(); + $this->assets->setCssPipeline(true); + $this->assets->addCss('/system/assets/debugger/phpdebugbar.css'); + $css = $this->assets->css('head', ['loading' => 'inline']); + self::assertStringContainsString('div.phpdebugbar', $css); + + $this->assets->reset(); + $this->assets->setCssPipeline(true); + $this->assets->addCss('https://fonts.googleapis.com/css?family=Roboto'); + $css = $this->assets->css('head', ['loading' => 'inline']); + self::assertStringContainsString('font-family:\'Roboto\';', $css); + + //Test adding media queries + $this->assets->reset(); + $this->assets->add('test.css', ['media' => 'only screen and (min-width: 640px)']); + $css = $this->assets->css(); + self::assertSame('' . PHP_EOL, $css); + } + + public function testAddingAssetPropertiesWithArray(): void + { + //Test adding assets with object to define properties + $this->assets->reset(); + $this->assets->addJs('test.js', ['loading' => 'async']); + $js = $this->assets->js(); + self::assertSame('' . PHP_EOL, $js); + $this->assets->reset(); + } + + public function testAddingJSAssetPropertiesWithArrayFromCollection(): void + { + //Test adding properties with array + $this->assets->reset(); + $this->assets->addJs('jquery', ['loading' => 'async']); + $js = $this->assets->js(); + self::assertSame('' . PHP_EOL, $js); + + //Test priority too + $this->assets->reset(); + $this->assets->addJs('jquery', ['loading' => 'async', 'priority' => 1]); + $this->assets->addJs('test.js', ['loading' => 'async', 'priority' => 2]); + $js = $this->assets->js(); + self::assertSame('' . PHP_EOL . + '' . PHP_EOL, $js); + + //Test multiple groups + $this->assets->reset(); + $this->assets->addJs('jquery', ['loading' => 'async', 'priority' => 1, 'group' => 'footer']); + $this->assets->addJs('test.js', ['loading' => 'async', 'priority' => 2]); + $js = $this->assets->js(); + self::assertSame('' . PHP_EOL, $js); + $js = $this->assets->js('footer'); + self::assertSame('' . PHP_EOL, $js); + + //Test adding array of assets + //Test priority too + $this->assets->reset(); + $this->assets->addJs(['jquery', 'test.js'], ['loading' => 'async']); + $js = $this->assets->js(); + + self::assertSame('' . PHP_EOL . + '' . PHP_EOL, $js); + } + + public function testAddingLegacyFormat(): void + { + // regular CSS add + //test addCss(). Test adding asset to a separate group + $this->assets->reset(); + $this->assets->addCSS('test.css', 15, true, 'bottom', 'async'); + $css = $this->assets->css('bottom'); + self::assertSame('' . PHP_EOL, $css); + + $array = $this->assets->getCss(); + /** @var Assets\BaseAsset $item */ + $item = reset($array); + $actual = json_encode($item); + $expected = ' + { + "type":"css", + "elements":{ + "asset":"\/test.css", + "asset_type":"css", + "order":0, + "group":"bottom", + "position":"pipeline", + "priority":15, + "attributes":{ + "type":"text\/css", + "rel":"stylesheet", + "loading":"async" + }, + "modified":false, + "query":"" + } + }'; + self::assertJsonStringEqualsJsonString($expected, $actual); + + $this->assets->reset(); + $this->assets->addJs('test.js', 15, false, 'defer', 'bottom'); + $js = $this->assets->js('bottom'); + self::assertSame('' . PHP_EOL, $js); + + $array = $this->assets->getJs(); + /** @var Assets\BaseAsset $item */ + $item = reset($array); + $actual = json_encode($item); + $expected = ' + { + "type": "js", + "elements": { + "asset": "/test.js", + "asset_type": "js", + "order": 0, + "group": "bottom", + "position": "after", + "priority": 15, + "attributes": { + "loading": "defer" + }, + "modified": false, + "query": "" + } + }'; + self::assertJsonStringEqualsJsonString($expected, $actual); + + + $this->assets->reset(); + $this->assets->addInlineCss('body { color: black }', 15, 'bottom'); + $css = $this->assets->css('bottom'); + self::assertSame('' . PHP_EOL, $css); + + $this->assets->reset(); + $this->assets->addInlineJs('alert("test")', 15, 'bottom', ['id' => 'foo']); + $js = $this->assets->js('bottom'); + self::assertSame('' . PHP_EOL, $js); + } + + public function testAddingCSSAssetPropertiesWithArrayFromCollection(): void + { + $this->assets->registerCollection('test', ['/system/assets/whoops.css']); + + //Test priority too + $this->assets->reset(); + $this->assets->addCss('test', ['priority' => 1]); + $this->assets->addCss('test.css', ['priority' => 2]); + $css = $this->assets->css(); + self::assertSame('' . PHP_EOL . + '' . PHP_EOL, $css); + + //Test multiple groups + $this->assets->reset(); + $this->assets->addCss('test', ['priority' => 1, 'group' => 'footer']); + $this->assets->addCss('test.css', ['priority' => 2]); + $css = $this->assets->css(); + self::assertSame('' . PHP_EOL, $css); + $css = $this->assets->css('footer'); + self::assertSame('' . PHP_EOL, $css); + + //Test adding array of assets + //Test priority too + $this->assets->reset(); + $this->assets->addCss(['test', 'test.css'], ['loading' => 'async']); + $css = $this->assets->css(); + self::assertSame('' . PHP_EOL . + '' . PHP_EOL, $css); + } + + public function testAddingAssetPropertiesWithArrayFromCollectionAndParameters(): void + { + $this->assets->registerCollection('collection_multi_params', [ + 'foo.js' => [ 'defer' => true ], + 'bar.js' => [ 'integrity' => 'sha512-abc123' ], + 'foobar.css' => [ 'defer' => null, 'loading' => null ] + ]); + + // # Test adding properties with array + $this->assets->addJs('collection_multi_params', ['loading' => 'async']); + $js = $this->assets->js(); + + // expected output + $expected = [ + '', + '', + '', + ]; + + self::assertCount(count($expected), array_filter(explode("\n", $js))); + self::assertSame(implode("\n", $expected) . PHP_EOL, $js); + + // # Test priority as second argument + render JS should not have any css + $this->assets->reset(); + $this->assets->add('low_priority.js', 1); + $this->assets->add('collection_multi_params', 2); + $js = $this->assets->js(); + + // expected output + $expected = [ + '', + '', + '', + ]; + + self::assertCount(3, array_filter(explode("\n", $js))); + self::assertSame(implode("\n", $expected) . PHP_EOL, $js); + + // # Test rendering CSS, should not have any JS + $this->assets->reset(); + $this->assets->add('collection_multi_params', [ 'class' => '__classname' ]); + $css = $this->assets->css(); + + // expected output + $expected = [ + '', + ]; + + + self::assertCount(1, array_filter(explode("\n", $css))); + self::assertSame(implode("\n", $expected) . PHP_EOL, $css); + } + + public function testPriorityOfAssets(): void + { + $this->assets->reset(); + $this->assets->add('test.css'); + $this->assets->add('test-after.css'); + + $css = $this->assets->css(); + self::assertSame('' . PHP_EOL . + '' . PHP_EOL, $css); + + //---------------- + $this->assets->reset(); + $this->assets->add('test-after.css', 1); + $this->assets->add('test.css', 2); + + $css = $this->assets->css(); + self::assertSame('' . PHP_EOL . + '' . PHP_EOL, $css); + + //---------------- + $this->assets->reset(); + $this->assets->add('test-after.css', 1); + $this->assets->add('test.css', 2); + $this->assets->add('test-before.css', 3); + + $css = $this->assets->css(); + self::assertSame('' . PHP_EOL . + '' . PHP_EOL . + '' . PHP_EOL, $css); + } + + public function testPipeline(): void + { + $this->assets->reset(); + + //File not existing. Pipeline searches for that file without reaching it. Output is empty. + $this->assets->add('test.css', null, true); + $this->assets->setCssPipeline(true); + $css = $this->assets->css(); + self::assertRegExp('##', $css); + + //Add a core Grav CSS file, which is found. Pipeline will now return a file + $this->assets->add('/system/assets/debugger/phpdebugbar', null, true); + $css = $this->assets->css(); + self::assertRegExp('##', $css); + } + + public function testPipelineWithTimestamp(): void + { + $this->assets->reset(); + $this->assets->setTimestamp('foo'); + $this->assets->setCssPipeline(true); + + //Add a core Grav CSS file, which is found. Pipeline will now return a file + $this->assets->add('/system/assets/debugger.css', null, true); + $css = $this->assets->css(); + self::assertRegExp('##', $css); + } + + public function testInline(): void + { + $this->assets->reset(); + + //File not existing. Pipeline searches for that file without reaching it. Output is empty. + $this->assets->add('test.css', ['loading' => 'inline']); + $css = $this->assets->css(); + self::assertSame("\n", $css); + + $this->assets->reset(); + //Add a core Grav CSS file, which is found. Pipeline will now return its content. + $this->assets->addCss('https://fonts.googleapis.com/css?family=Roboto', ['loading' => 'inline']); + $this->assets->addCss('/system/assets/debugger/phpdebugbar.css', ['loading' => 'inline']); + $css = $this->assets->css(); + self::assertStringContainsString('font-family: \'Roboto\';', $css); + self::assertStringContainsString('div.phpdebugbar-header', $css); + } + + public function testInlinePipeline(): void + { + $this->assets->reset(); + $this->assets->setCssPipeline(true); + + //File not existing. Pipeline searches for that file without reaching it. Output is empty. + $this->assets->add('test.css'); + $css = $this->assets->css('head', ['loading' => 'inline']); + self::assertSame("\n", $css); + + //Add a core Grav CSS file, which is found. Pipeline will now return its content. + $this->assets->addCss('https://fonts.googleapis.com/css?family=Roboto', null, true); + $this->assets->add('/system/assets/debugger/phpdebugbar.css', null, true); + $css = $this->assets->css('head', ['loading' => 'inline']); + self::assertStringContainsString('font-family:\'Roboto\';', $css); + self::assertStringContainsString('div.phpdebugbar', $css); + } + + public function testAddAsyncJs(): void + { + $this->assets->reset(); + $this->assets->addAsyncJs('jquery'); + $js = $this->assets->js(); + self::assertSame('' . PHP_EOL, $js); + } + + public function testAddDeferJs(): void + { + $this->assets->reset(); + $this->assets->addDeferJs('jquery'); + $js = $this->assets->js(); + self::assertSame('' . PHP_EOL, $js); + } + + public function testTimestamps(): void + { + // local CSS nothing extra + $this->assets->reset(); + $this->assets->setTimestamp('foo'); + $this->assets->addCSS('test.css'); + $css = $this->assets->css(); + self::assertSame('' . PHP_EOL, $css); + + // local CSS already with param + $this->assets->reset(); + $this->assets->setTimestamp('foo'); + $this->assets->addCSS('test.css?bar'); + $css = $this->assets->css(); + self::assertSame('' . PHP_EOL, $css); + + // external CSS already + $this->assets->reset(); + $this->assets->setTimestamp('foo'); + $this->assets->addCSS('http://somesite.com/test.css'); + $css = $this->assets->css(); + self::assertSame('' . PHP_EOL, $css); + + // external CSS already with param + $this->assets->reset(); + $this->assets->setTimestamp('foo'); + $this->assets->addCSS('http://somesite.com/test.css?bar'); + $css = $this->assets->css(); + self::assertSame('' . PHP_EOL, $css); + + // local JS nothing extra + $this->assets->reset(); + $this->assets->setTimestamp('foo'); + $this->assets->addJs('test.js'); + $css = $this->assets->js(); + self::assertSame('' . PHP_EOL, $css); + + // local JS already with param + $this->assets->reset(); + $this->assets->setTimestamp('foo'); + $this->assets->addJs('test.js?bar'); + $css = $this->assets->js(); + self::assertSame('' . PHP_EOL, $css); + + // external JS already + $this->assets->reset(); + $this->assets->setTimestamp('foo'); + $this->assets->addJs('http://somesite.com/test.js'); + $css = $this->assets->js(); + self::assertSame('' . PHP_EOL, $css); + + // external JS already with param + $this->assets->reset(); + $this->assets->setTimestamp('foo'); + $this->assets->addJs('http://somesite.com/test.js?bar'); + $css = $this->assets->js(); + self::assertSame('' . PHP_EOL, $css); + } + + public function testAddInlineCss(): void + { + $this->assets->reset(); + $this->assets->addInlineCss('body { color: black }'); + $css = $this->assets->css(); + self::assertSame('' . PHP_EOL, $css); + } + + public function testAddInlineJs(): void + { + $this->assets->reset(); + $this->assets->addInlineJs('alert("test")'); + $js = $this->assets->js(); + self::assertSame('' . PHP_EOL, $js); + } + + public function testGetCollections(): void + { + self::assertIsArray($this->assets->getCollections()); + self::assertContains('jquery', array_keys($this->assets->getCollections())); + self::assertContains('system://assets/jquery/jquery-3.x.min.js', $this->assets->getCollections()); + } + + public function testExists(): void + { + self::assertTrue($this->assets->exists('jquery')); + self::assertFalse($this->assets->exists('another-unexisting-library')); + } + + public function testRegisterCollection(): void + { + $this->assets->registerCollection('debugger', ['/system/assets/debugger.css']); + self::assertTrue($this->assets->exists('debugger')); + self::assertContains('debugger', array_keys($this->assets->getCollections())); + } + + public function testRegisterCollectionWithParameters(): void + { + $this->assets->registerCollection('collection_multi_params', [ + 'foo.js' => [ 'defer' => true ], + 'bar.js' => [ 'integrity' => 'sha512-abc123' ], + 'foobar.css' => [ 'defer' => null ], + ]); + + self::assertTrue($this->assets->exists('collection_multi_params')); + + $collection = $this->assets->getCollections()['collection_multi_params']; + self::assertArrayHasKey('foo.js', $collection); + self::assertArrayHasKey('bar.js', $collection); + self::assertArrayHasKey('foobar.css', $collection); + self::assertArrayHasKey('defer', $collection['foo.js']); + self::assertArrayHasKey('defer', $collection['foobar.css']); + + self::assertNull($collection['foobar.css']['defer']); + self::assertTrue($collection['foo.js']['defer']); + } + + public function testReset(): void + { + $this->assets->addInlineJs('alert("test")'); + $this->assets->reset(); + self::assertCount(0, (array) $this->assets->getJs()); + + $this->assets->addAsyncJs('jquery'); + $this->assets->reset(); + self::assertCount(0, (array) $this->assets->getJs()); + + $this->assets->addInlineCss('body { color: black }'); + $this->assets->reset(); + self::assertCount(0, (array) $this->assets->getCss()); + + $this->assets->add('/system/assets/debugger.css', null, true); + $this->assets->reset(); + self::assertCount(0, (array) $this->assets->getCss()); + } + + public function testResetJs(): void + { + $this->assets->addInlineJs('alert("test")'); + $this->assets->resetJs(); + self::assertCount(0, (array) $this->assets->getJs()); + + $this->assets->addAsyncJs('jquery'); + $this->assets->resetJs(); + self::assertCount(0, (array) $this->assets->getJs()); + } + + public function testResetCss(): void + { + $this->assets->addInlineCss('body { color: black }'); + $this->assets->resetCss(); + self::assertCount(0, (array) $this->assets->getCss()); + + $this->assets->add('/system/assets/debugger.css', null, true); + $this->assets->resetCss(); + self::assertCount(0, (array) $this->assets->getCss()); + } + + public function testAddDirCss(): void + { + $this->assets->addDirCss('/system'); + + self::assertIsArray($this->assets->getCss()); + self::assertGreaterThan(0, (array) $this->assets->getCss()); + self::assertIsArray($this->assets->getJs()); + self::assertCount(0, (array) $this->assets->getJs()); + + $this->assets->reset(); + $this->assets->addDirCss('/system/assets'); + + self::assertIsArray($this->assets->getCss()); + self::assertGreaterThan(0, (array) $this->assets->getCss()); + self::assertIsArray($this->assets->getJs()); + self::assertCount(0, (array) $this->assets->getJs()); + + $this->assets->reset(); + $this->assets->addDirJs('/system'); + + self::assertIsArray($this->assets->getCss()); + self::assertCount(0, (array) $this->assets->getCss()); + self::assertIsArray($this->assets->getJs()); + self::assertGreaterThan(0, (array) $this->assets->getJs()); + + $this->assets->reset(); + $this->assets->addDirJs('/system/assets'); + + self::assertIsArray($this->assets->getCss()); + self::assertCount(0, (array) $this->assets->getCss()); + self::assertIsArray($this->assets->getJs()); + self::assertGreaterThan(0, (array) $this->assets->getJs()); + + $this->assets->reset(); + $this->assets->addDir('/system/assets'); + + self::assertIsArray($this->assets->getCss()); + self::assertGreaterThan(0, (array) $this->assets->getCss()); + self::assertIsArray($this->assets->getJs()); + self::assertGreaterThan(0, (array) $this->assets->getJs()); + + //Use streams + $this->assets->reset(); + $this->assets->addDir('system://assets'); + + self::assertIsArray($this->assets->getCss()); + self::assertGreaterThan(0, (array) $this->assets->getCss()); + self::assertIsArray($this->assets->getJs()); + self::assertGreaterThan(0, (array) $this->assets->getJs()); + } +} diff --git a/tests/unit/Grav/Common/BrowserTest.php b/tests/unit/Grav/Common/BrowserTest.php new file mode 100644 index 0000000..a1033d8 --- /dev/null +++ b/tests/unit/Grav/Common/BrowserTest.php @@ -0,0 +1,51 @@ +grav = $grav(); + } + + protected function _after(): void + { + } + + public function testGetBrowser(): void + { + /* Already covered by PhpUserAgent tests */ + } + + public function testGetPlatform(): void + { + /* Already covered by PhpUserAgent tests */ + } + + public function testGetLongVersion(): void + { + /* Already covered by PhpUserAgent tests */ + } + + public function testGetVersion(): void + { + /* Already covered by PhpUserAgent tests */ + } + + public function testIsHuman(): void + { + //Already Partially covered by PhpUserAgent tests + + //Make sure it recognizes the test as not human + self::assertFalse($this->grav['browser']->isHuman()); + } +} diff --git a/tests/unit/Grav/Common/ComposerTest.php b/tests/unit/Grav/Common/ComposerTest.php new file mode 100644 index 0000000..8c73a1f --- /dev/null +++ b/tests/unit/Grav/Common/ComposerTest.php @@ -0,0 +1,31 @@ +loadBlueprint('strict'); + + $blueprint->validate(['test' => 'string']); + } + + /** + * @depends testValidateStrict + */ + public function testValidateStrictRequired(): void + { + $blueprint = $this->loadBlueprint('strict'); + + $this->expectException(\Grav\Common\Data\ValidationException::class); + $blueprint->validate([]); + } + + /** + * @depends testValidateStrict + */ + public function testValidateStrictExtra(): void + { + $blueprint = $this->loadBlueprint('strict'); + + $blueprint->validate(['test' => 'string', 'wrong' => 'field']); + } + + /** + * @depends testValidateStrict + */ + public function testValidateStrictExtraException(): void + { + $blueprint = $this->loadBlueprint('strict'); + + /** @var Config $config */ + $config = Grav::instance()['config']; + $var = 'system.strict_mode.blueprint_strict_compat'; + $config->set($var, false); + + $this->expectException(\Grav\Common\Data\ValidationException::class); + $blueprint->validate(['test' => 'string', 'wrong' => 'field']); + + $config->set($var, true); + } + + /** + * @param string $filename + * @return Blueprint + */ + protected function loadBlueprint($filename): Blueprint + { + $blueprint = new Blueprint('strict'); + $blueprint->setContext(dirname(__DIR__, 3). '/data/blueprints'); + $blueprint->load()->init(); + + return $blueprint; + } +} diff --git a/tests/unit/Grav/Common/GPM/GPMTest.php b/tests/unit/Grav/Common/GPM/GPMTest.php new file mode 100644 index 0000000..684ed3d --- /dev/null +++ b/tests/unit/Grav/Common/GPM/GPMTest.php @@ -0,0 +1,329 @@ +data[$search] ?? false; + } + + /** + * @inheritdoc + */ + public function findPackages($searches = []) + { + return $this->data; + } +} + +/** + * Class InstallCommandTest + */ +class GpmTest extends \Codeception\TestCase\Test +{ + /** @var Grav $grav */ + protected $grav; + + /** @var GpmStub */ + protected $gpm; + + protected function _before(): void + { + $this->grav = Fixtures::get('grav'); + $this->gpm = new GpmStub(); + } + + protected function _after(): void + { + } + + public function testCalculateMergedDependenciesOfPackages(): void + { + ////////////////////////////////////////////////////////////////////////////////////////// + // First working example + ////////////////////////////////////////////////////////////////////////////////////////// + $this->gpm->data = [ + 'admin' => (object)[ + 'dependencies' => [ + ['name' => 'grav', 'version' => '>=1.0.10'], + ['name' => 'form', 'version' => '~2.0'], + ['name' => 'login', 'version' => '>=2.0'], + ['name' => 'errors', 'version' => '*'], + ['name' => 'problems'], + ] + ], + 'test' => (object)[ + 'dependencies' => [ + ['name' => 'errors', 'version' => '>=1.0'] + ] + ], + 'grav', + 'form' => (object)[ + 'dependencies' => [ + ['name' => 'errors', 'version' => '>=3.2'] + ] + ] + + + ]; + + $packages = ['admin', 'test']; + + $dependencies = $this->gpm->calculateMergedDependenciesOfPackages($packages); + + self::assertIsArray($dependencies); + self::assertCount(5, $dependencies); + + self::assertSame('>=1.0.10', $dependencies['grav']); + self::assertArrayHasKey('errors', $dependencies); + self::assertArrayHasKey('problems', $dependencies); + + ////////////////////////////////////////////////////////////////////////////////////////// + // Second working example + ////////////////////////////////////////////////////////////////////////////////////////// + $packages = ['admin', 'form']; + + $dependencies = $this->gpm->calculateMergedDependenciesOfPackages($packages); + self::assertIsArray($dependencies); + self::assertCount(5, $dependencies); + self::assertSame('>=3.2', $dependencies['errors']); + + ////////////////////////////////////////////////////////////////////////////////////////// + // Third working example + ////////////////////////////////////////////////////////////////////////////////////////// + $this->gpm->data = [ + + 'admin' => (object)[ + 'dependencies' => [ + ['name' => 'errors', 'version' => '>=4.0'], + ] + ], + 'test' => (object)[ + 'dependencies' => [ + ['name' => 'errors', 'version' => '>=1.0'] + ] + ], + 'another' => (object)[ + 'dependencies' => [ + ['name' => 'errors', 'version' => '>=3.2'] + ] + ] + + ]; + + $packages = ['admin', 'test', 'another']; + + + $dependencies = $this->gpm->calculateMergedDependenciesOfPackages($packages); + self::assertIsArray($dependencies); + self::assertCount(1, $dependencies); + self::assertSame('>=4.0', $dependencies['errors']); + + + + ////////////////////////////////////////////////////////////////////////////////////////// + // Test alpha / beta / rc + ////////////////////////////////////////////////////////////////////////////////////////// + $this->gpm->data = [ + 'admin' => (object)[ + 'dependencies' => [ + ['name' => 'package1', 'version' => '>=4.0.0-rc1'], + ['name' => 'package4', 'version' => '>=3.2.0'], + ] + ], + 'test' => (object)[ + 'dependencies' => [ + ['name' => 'package1', 'version' => '>=4.0.0-rc2'], + ['name' => 'package2', 'version' => '>=3.2.0-alpha'], + ['name' => 'package3', 'version' => '>=3.2.0-alpha.2'], + ['name' => 'package4', 'version' => '>=3.2.0-alpha'], + ] + ], + 'another' => (object)[ + 'dependencies' => [ + ['name' => 'package2', 'version' => '>=3.2.0-beta.11'], + ['name' => 'package3', 'version' => '>=3.2.0-alpha.1'], + ['name' => 'package4', 'version' => '>=3.2.0-beta'], + ] + ] + ]; + + $packages = ['admin', 'test', 'another']; + + + $dependencies = $this->gpm->calculateMergedDependenciesOfPackages($packages); + self::assertSame('>=4.0.0-rc2', $dependencies['package1']); + self::assertSame('>=3.2.0-beta.11', $dependencies['package2']); + self::assertSame('>=3.2.0-alpha.2', $dependencies['package3']); + self::assertSame('>=3.2.0', $dependencies['package4']); + + + ////////////////////////////////////////////////////////////////////////////////////////// + // Raise exception if no version is specified + ////////////////////////////////////////////////////////////////////////////////////////// + $this->gpm->data = [ + + 'admin' => (object)[ + 'dependencies' => [ + ['name' => 'errors', 'version' => '>=4.0'], + ] + ], + 'test' => (object)[ + 'dependencies' => [ + ['name' => 'errors', 'version' => '>='] + ] + ], + + ]; + + $packages = ['admin', 'test']; + + try { + $this->gpm->calculateMergedDependenciesOfPackages($packages); + self::fail('Expected Exception not thrown'); + } catch (Exception $e) { + self::assertEquals(EXCEPTION_BAD_FORMAT, $e->getCode()); + self::assertStringStartsWith('Bad format for version of dependency', $e->getMessage()); + } + + ////////////////////////////////////////////////////////////////////////////////////////// + // Raise exception if incompatible versions are specified + ////////////////////////////////////////////////////////////////////////////////////////// + $this->gpm->data = [ + 'admin' => (object)[ + 'dependencies' => [ + ['name' => 'errors', 'version' => '~4.0'], + ] + ], + 'test' => (object)[ + 'dependencies' => [ + ['name' => 'errors', 'version' => '~3.0'] + ] + ], + ]; + + $packages = ['admin', 'test']; + + try { + $this->gpm->calculateMergedDependenciesOfPackages($packages); + self::fail('Expected Exception not thrown'); + } catch (Exception $e) { + self::assertEquals(EXCEPTION_INCOMPATIBLE_VERSIONS, $e->getCode()); + self::assertStringEndsWith('required in two incompatible versions', $e->getMessage()); + } + + ////////////////////////////////////////////////////////////////////////////////////////// + // Test dependencies of dependencies + ////////////////////////////////////////////////////////////////////////////////////////// + $this->gpm->data = [ + 'admin' => (object)[ + 'dependencies' => [ + ['name' => 'grav', 'version' => '>=1.0.10'], + ['name' => 'form', 'version' => '~2.0'], + ['name' => 'login', 'version' => '>=2.0'], + ['name' => 'errors', 'version' => '*'], + ['name' => 'problems'], + ] + ], + 'login' => (object)[ + 'dependencies' => [ + ['name' => 'antimatter', 'version' => '>=1.0'] + ] + ], + 'grav', + 'antimatter' => (object)[ + 'dependencies' => [ + ['name' => 'something', 'version' => '>=3.2'] + ] + ] + + + ]; + + $packages = ['admin']; + + $dependencies = $this->gpm->calculateMergedDependenciesOfPackages($packages); + + self::assertIsArray($dependencies); + self::assertCount(7, $dependencies); + + self::assertSame('>=1.0.10', $dependencies['grav']); + self::assertArrayHasKey('errors', $dependencies); + self::assertArrayHasKey('problems', $dependencies); + self::assertArrayHasKey('antimatter', $dependencies); + self::assertArrayHasKey('something', $dependencies); + self::assertSame('>=3.2', $dependencies['something']); + } + + public function testVersionFormatIsNextSignificantRelease(): void + { + self::assertFalse($this->gpm->versionFormatIsNextSignificantRelease('>=1.0')); + self::assertFalse($this->gpm->versionFormatIsNextSignificantRelease('>=2.3.4')); + self::assertFalse($this->gpm->versionFormatIsNextSignificantRelease('>=2.3.x')); + self::assertFalse($this->gpm->versionFormatIsNextSignificantRelease('1.0')); + self::assertTrue($this->gpm->versionFormatIsNextSignificantRelease('~2.3.x')); + self::assertTrue($this->gpm->versionFormatIsNextSignificantRelease('~2.0')); + } + + public function testVersionFormatIsEqualOrHigher(): void + { + self::assertTrue($this->gpm->versionFormatIsEqualOrHigher('>=1.0')); + self::assertTrue($this->gpm->versionFormatIsEqualOrHigher('>=2.3.4')); + self::assertTrue($this->gpm->versionFormatIsEqualOrHigher('>=2.3.x')); + self::assertFalse($this->gpm->versionFormatIsEqualOrHigher('~2.3.x')); + self::assertFalse($this->gpm->versionFormatIsEqualOrHigher('1.0')); + } + + public function testCheckNextSignificantReleasesAreCompatible(): void + { + /* + * ~1.0 is equivalent to >=1.0 < 2.0.0 + * ~1.2 is equivalent to >=1.2 <2.0.0 + * ~1.2.3 is equivalent to >=1.2.3 <1.3.0 + */ + self::assertTrue($this->gpm->checkNextSignificantReleasesAreCompatible('1.0', '1.2')); + self::assertTrue($this->gpm->checkNextSignificantReleasesAreCompatible('1.2', '1.0')); + self::assertTrue($this->gpm->checkNextSignificantReleasesAreCompatible('1.0', '1.0.10')); + self::assertTrue($this->gpm->checkNextSignificantReleasesAreCompatible('1.1', '1.1.10')); + self::assertTrue($this->gpm->checkNextSignificantReleasesAreCompatible('30.0', '30.10')); + self::assertTrue($this->gpm->checkNextSignificantReleasesAreCompatible('1.0', '1.1.10')); + self::assertTrue($this->gpm->checkNextSignificantReleasesAreCompatible('1.0', '1.8')); + self::assertTrue($this->gpm->checkNextSignificantReleasesAreCompatible('1.0.1', '1.1')); + self::assertTrue($this->gpm->checkNextSignificantReleasesAreCompatible('2.0.0-beta', '2.0')); + self::assertTrue($this->gpm->checkNextSignificantReleasesAreCompatible('2.0.0-rc.1', '2.0')); + self::assertTrue($this->gpm->checkNextSignificantReleasesAreCompatible('2.0', '2.0.0-alpha')); + + self::assertFalse($this->gpm->checkNextSignificantReleasesAreCompatible('1.0', '2.2')); + self::assertFalse($this->gpm->checkNextSignificantReleasesAreCompatible('1.0.0-beta.1', '2.0')); + self::assertFalse($this->gpm->checkNextSignificantReleasesAreCompatible('0.9.99', '1.0.0')); + self::assertFalse($this->gpm->checkNextSignificantReleasesAreCompatible('0.9.99', '1.0.10')); + self::assertFalse($this->gpm->checkNextSignificantReleasesAreCompatible('0.9.99', '1.0.10.2')); + } + + public function testCalculateVersionNumberFromDependencyVersion(): void + { + self::assertSame('2.0', $this->gpm->calculateVersionNumberFromDependencyVersion('>=2.0')); + self::assertSame('2.0.2', $this->gpm->calculateVersionNumberFromDependencyVersion('>=2.0.2')); + self::assertSame('2.0.2', $this->gpm->calculateVersionNumberFromDependencyVersion('~2.0.2')); + self::assertSame('1', $this->gpm->calculateVersionNumberFromDependencyVersion('~1')); + self::assertNull($this->gpm->calculateVersionNumberFromDependencyVersion('')); + self::assertNull($this->gpm->calculateVersionNumberFromDependencyVersion('*')); + self::assertSame('2.0.2', $this->gpm->calculateVersionNumberFromDependencyVersion('2.0.2')); + } +} diff --git a/tests/unit/Grav/Common/Helpers/ExcerptsTest.php b/tests/unit/Grav/Common/Helpers/ExcerptsTest.php new file mode 100644 index 0000000..8cb473c --- /dev/null +++ b/tests/unit/Grav/Common/Helpers/ExcerptsTest.php @@ -0,0 +1,120 @@ +grav = $grav(); + $this->pages = $this->grav['pages']; + $this->config = $this->grav['config']; + $this->uri = $this->grav['uri']; + $this->language = $this->grav['language']; + $this->old_home = $this->config->get('system.home.alias'); + $this->config->set('system.home.alias', '/item1'); + $this->config->set('system.absolute_urls', false); + $this->config->set('system.languages.supported', []); + + unset($this->grav['language']); + $this->grav['language'] = new Language($this->grav); + + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + $locator->addPath('page', '', 'tests/fake/nested-site/user/pages', false); + $this->pages->init(); + + $defaults = [ + 'extra' => false, + 'auto_line_breaks' => false, + 'auto_url_links' => false, + 'escape_markup' => false, + 'special_chars' => ['>' => 'gt', '<' => 'lt'], + ]; + $this->page = $this->pages->find('/item2/item2-2'); + $this->uri->initializeWithURL('http://testing.dev/item2/item2-2')->init(); + } + + protected function _after(): void + { + $this->config->set('system.home.alias', $this->old_home); + } + + + public function testProcessImageHtml(): void + { + self::assertRegexp( + '|Sample Image|', + Excerpts::processImageHtml('Sample Image', $this->page) + ); + self::assertRegexp( + '|Sample Image|', + Excerpts::processImageHtml('Sample Image', $this->page) + ); + } + + public function testNoProcess(): void + { + self::assertStringStartsWith( + 'regular process') + ); + + self::assertStringStartsWith( + 'noprocess') + ); + + self::assertStringStartsWith( + 'noprocess=id') + ); + } + + public function testTarget(): void + { + self::assertStringStartsWith( + 'only target') + ); + self::assertStringStartsWith( + 'target and rel') + ); + } +} diff --git a/tests/unit/Grav/Common/InflectorTest.php b/tests/unit/Grav/Common/InflectorTest.php new file mode 100644 index 0000000..63e6e99 --- /dev/null +++ b/tests/unit/Grav/Common/InflectorTest.php @@ -0,0 +1,147 @@ +grav = $grav(); + $this->inflector = $this->grav['inflector']; + } + + protected function _after(): void + { + } + + public function testPluralize(): void + { + self::assertSame('words', $this->inflector->pluralize('word')); + self::assertSame('kisses', $this->inflector->pluralize('kiss')); + self::assertSame('volcanoes', $this->inflector->pluralize('volcanoe')); + self::assertSame('cherries', $this->inflector->pluralize('cherry')); + self::assertSame('days', $this->inflector->pluralize('day')); + self::assertSame('knives', $this->inflector->pluralize('knife')); + } + + public function testSingularize(): void + { + self::assertSame('word', $this->inflector->singularize('words')); + self::assertSame('kiss', $this->inflector->singularize('kisses')); + self::assertSame('volcanoe', $this->inflector->singularize('volcanoe')); + self::assertSame('cherry', $this->inflector->singularize('cherries')); + self::assertSame('day', $this->inflector->singularize('days')); + self::assertSame('knife', $this->inflector->singularize('knives')); + } + + public function testTitleize(): void + { + self::assertSame('This String Is Titleized', $this->inflector->titleize('ThisStringIsTitleized')); + self::assertSame('This String Is Titleized', $this->inflector->titleize('this string is titleized')); + self::assertSame('This String Is Titleized', $this->inflector->titleize('this_string_is_titleized')); + self::assertSame('This String Is Titleized', $this->inflector->titleize('this-string-is-titleized')); + self::assertSame('Échelle Synoptique', $this->inflector->titleize('échelle synoptique')); + + self::assertSame('This string is titleized', $this->inflector->titleize('ThisStringIsTitleized', 'first')); + self::assertSame('This string is titleized', $this->inflector->titleize('this string is titleized', 'first')); + self::assertSame('This string is titleized', $this->inflector->titleize('this_string_is_titleized', 'first')); + self::assertSame('This string is titleized', $this->inflector->titleize('this-string-is-titleized', 'first')); + self::assertSame('Échelle synoptique', $this->inflector->titleize('échelle synoptique', 'first')); + } + + public function testCamelize(): void + { + self::assertSame('ThisStringIsCamelized', $this->inflector->camelize('This String Is Camelized')); + self::assertSame('ThisStringIsCamelized', $this->inflector->camelize('thisStringIsCamelized')); + self::assertSame('ThisStringIsCamelized', $this->inflector->camelize('This_String_Is_Camelized')); + self::assertSame('ThisStringIsCamelized', $this->inflector->camelize('this string is camelized')); + self::assertSame('GravSPrettyCoolMy1', $this->inflector->camelize("Grav's Pretty Cool. My #1!")); + } + + public function testUnderscorize(): void + { + self::assertSame('this_string_is_underscorized', $this->inflector->underscorize('This String Is Underscorized')); + self::assertSame('this_string_is_underscorized', $this->inflector->underscorize('ThisStringIsUnderscorized')); + self::assertSame('this_string_is_underscorized', $this->inflector->underscorize('This_String_Is_Underscorized')); + self::assertSame('this_string_is_underscorized', $this->inflector->underscorize('This-String-Is-Underscorized')); + } + + public function testHyphenize(): void + { + self::assertSame('this-string-is-hyphenized', $this->inflector->hyphenize('This String Is Hyphenized')); + self::assertSame('this-string-is-hyphenized', $this->inflector->hyphenize('ThisStringIsHyphenized')); + self::assertSame('this-string-is-hyphenized', $this->inflector->hyphenize('This-String-Is-Hyphenized')); + self::assertSame('this-string-is-hyphenized', $this->inflector->hyphenize('This_String_Is_Hyphenized')); + } + + public function testHumanize(): void + { + //self::assertSame('This string is humanized', $this->inflector->humanize('ThisStringIsHumanized')); + self::assertSame('This string is humanized', $this->inflector->humanize('this_string_is_humanized')); + //self::assertSame('This string is humanized', $this->inflector->humanize('this-string-is-humanized')); + + self::assertSame('This String Is Humanized', $this->inflector->humanize('this_string_is_humanized', 'all')); + //self::assertSame('This String Is Humanized', $this->inflector->humanize('this-string-is-humanized'), 'all'); + } + + public function testVariablize(): void + { + self::assertSame('thisStringIsVariablized', $this->inflector->variablize('This String Is Variablized')); + self::assertSame('thisStringIsVariablized', $this->inflector->variablize('ThisStringIsVariablized')); + self::assertSame('thisStringIsVariablized', $this->inflector->variablize('This_String_Is_Variablized')); + self::assertSame('thisStringIsVariablized', $this->inflector->variablize('this string is variablized')); + self::assertSame('gravSPrettyCoolMy1', $this->inflector->variablize("Grav's Pretty Cool. My #1!")); + } + + public function testTableize(): void + { + self::assertSame('people', $this->inflector->tableize('Person')); + self::assertSame('pages', $this->inflector->tableize('Page')); + self::assertSame('blog_pages', $this->inflector->tableize('BlogPage')); + self::assertSame('admin_dependencies', $this->inflector->tableize('adminDependency')); + self::assertSame('admin_dependencies', $this->inflector->tableize('admin-dependency')); + self::assertSame('admin_dependencies', $this->inflector->tableize('admin_dependency')); + } + + public function testClassify(): void + { + self::assertSame('Person', $this->inflector->classify('people')); + self::assertSame('Page', $this->inflector->classify('pages')); + self::assertSame('BlogPage', $this->inflector->classify('blog_pages')); + self::assertSame('AdminDependency', $this->inflector->classify('admin_dependencies')); + } + + public function testOrdinalize(): void + { + self::assertSame('1st', $this->inflector->ordinalize(1)); + self::assertSame('2nd', $this->inflector->ordinalize(2)); + self::assertSame('3rd', $this->inflector->ordinalize(3)); + self::assertSame('4th', $this->inflector->ordinalize(4)); + self::assertSame('5th', $this->inflector->ordinalize(5)); + self::assertSame('16th', $this->inflector->ordinalize(16)); + self::assertSame('51st', $this->inflector->ordinalize(51)); + self::assertSame('111th', $this->inflector->ordinalize(111)); + self::assertSame('123rd', $this->inflector->ordinalize(123)); + } + + public function testMonthize(): void + { + self::assertSame(0, $this->inflector->monthize(10)); + self::assertSame(1, $this->inflector->monthize(33)); + self::assertSame(1, $this->inflector->monthize(41)); + self::assertSame(11, $this->inflector->monthize(364)); + } +} diff --git a/tests/unit/Grav/Common/Language/LanguageCodesTest.php b/tests/unit/Grav/Common/Language/LanguageCodesTest.php new file mode 100644 index 0000000..3450c88 --- /dev/null +++ b/tests/unit/Grav/Common/Language/LanguageCodesTest.php @@ -0,0 +1,27 @@ +grav = $grav(); + $this->pages = $this->grav['pages']; + $this->config = $this->grav['config']; + $this->uri = $this->grav['uri']; + $this->language = $this->grav['language']; + $this->old_home = $this->config->get('system.home.alias'); + $this->config->set('system.home.alias', '/item1'); + $this->config->set('system.absolute_urls', false); + $this->config->set('system.languages.supported', []); + + unset($this->grav['language']); + $this->grav['language'] = new Language($this->grav); + + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + $locator->addPath('page', '', 'tests/fake/nested-site/user/pages', false); + $this->pages->init(); + + $defaults = [ + 'markdown' => [ + 'extra' => false, + 'auto_line_breaks' => false, + 'auto_url_links' => false, + 'escape_markup' => false, + 'special_chars' => ['>' => 'gt', '<' => 'lt'], + ], + 'images' => $this->config->get('system.images', []) + ]; + $page = $this->pages->find('/item2/item2-2'); + + $excerpts = new Excerpts($page, $defaults); + $this->parsedown = new Parsedown($excerpts); + } + + protected function _after(): void + { + $this->config->set('system.home.alias', $this->old_home); + } + + public function testImages(): void + { + $this->config->set('system.languages.supported', ['fr','en']); + unset($this->grav['language']); + $this->grav['language'] = new Language($this->grav); + $this->uri->initializeWithURL('http://testing.dev/fr/item2/item2-2')->init(); + + self::assertSame( + '

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

    <\/p>|', + $this->parsedown->text('![](cache-image.jpg?cropResize=200,200&foo)') + ); + + $this->uri->initializeWithURL('http://testing.dev/item2/item2-2')->init(); + + self::assertSame( + '

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

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

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

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

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

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

    ', + $this->parsedown->text('![](https://getgrav-grav.netdna-ssl.com/user/pages/media/grav-logo.svg)') + ); + } + + public function testImagesSubDir(): void + { + $this->config->set('system.images.cache_all', false); + $this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/item2/item2-2', '/subdir')->init(); + + self::assertRegexp( + '|

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    ', + $this->parsedown->text('![](/home-missing-image.jpg)') + ); + } + + public function testImagesAttributes(): void + { + $this->uri->initializeWithURL('http://testing.dev/item2/item2-2')->init(); + + self::assertSame( + '

    ', + $this->parsedown->text('![](sample-image.jpg "My Title")') + ); + self::assertSame( + '

    ', + $this->parsedown->text('![](sample-image.jpg?classes=foo)') + ); + self::assertSame( + '

    ', + $this->parsedown->text('![](sample-image.jpg?classes=foo,bar)') + ); + self::assertSame( + '

    ', + $this->parsedown->text('![](sample-image.jpg?id=foo)') + ); + self::assertSame( + '

    Alt Text

    ', + $this->parsedown->text('![Alt Text](sample-image.jpg?id=foo)') + ); + self::assertSame( + '

    Alt Text

    ', + $this->parsedown->text('![Alt Text](sample-image.jpg?class=bar&id=foo)') + ); + self::assertSame( + '

    Alt Text

    ', + $this->parsedown->text('![Alt Text](sample-image.jpg?class=bar&id=foo "My Title")') + ); + } + + public function testImagesDefaults(): void + { + /** + * Testing default 'loading' + */ + + $this->setImagesDefaults(['loading' => 'auto']); + + + // loading should NOT be added to image by default + self::assertSame( + '

    ', + $this->parsedown->text('![](sample-image.jpg)') + ); + + // loading="lazy" should be added when default is overridden by ?loading=lazy + self::assertSame( + '

    ', + $this->parsedown->text('![](sample-image.jpg?loading=lazy)') + ); + + $this->setImagesDefaults(['loading' => 'lazy']); + + // loading="lazy" should be added by default + self::assertSame( + '

    ', + $this->parsedown->text('![](sample-image.jpg)') + ); + + // loading should not be added when default is overridden by ?loading=auto + self::assertSame( + '

    ', + $this->parsedown->text('![](sample-image.jpg?loading=auto)') + ); + + // loading="eager" should be added when default is overridden by ?loading=eager + self::assertSame( + '

    ', + $this->parsedown->text('![](sample-image.jpg?loading=eager)') + ); + + } + + public function testCLSAutoSizes(): void + { + $this->config->set('system.images.cls.auto_sizes', false); + $this->uri->initializeWithURL('http://testing.dev/item2/item2-2')->init(); + + self::assertSame( + '

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

    ', + $this->parsedown->text('![](sample-image.jpg?height=1&width=1)') + ); + + self::assertSame( + '

    ', + $this->parsedown->text('![](sample-image.jpg?autoSizes=true)') + ); + + $this->config->set('system.images.cls.auto_sizes', true); + + self::assertSame( + '

    ', + $this->parsedown->text('![](sample-image.jpg?reset)') + ); + + self::assertSame( + '

    ', + $this->parsedown->text('![](sample-image.jpg?height=1&width=1)') + ); + + self::assertSame( + '

    ', + $this->parsedown->text('![](sample-image.jpg?autoSizes=false)') + ); + + self::assertRegExp( + '/width="400" height="200"/', + $this->parsedown->text('![](sample-image.jpg?reset&resize=400,200)') + ); + + $this->config->set('system.images.cls.retina_scale', 2); + + + self::assertRegExp( + '/width="400" height="200"/', + $this->parsedown->text('![](sample-image.jpg?reset&resize=800,400)') + ); + + $this->config->set('system.images.cls.retina_scale', 4); + + self::assertRegExp( + '/width="200" height="100"/', + $this->parsedown->text('![](sample-image.jpg?reset&resize=800,400)') + ); + + self::assertRegExp( + '/width="266" height="133"/', + $this->parsedown->text('![](sample-image.jpg?reset&resize=800,400&retinaScale=3)') + ); + + $this->config->set('system.images.cls.aspect_ratio', true); + + self::assertRegExp( + '/style="--aspect-ratio: 800\/400;"/', + $this->parsedown->text('![](sample-image.jpg?reset&resize=800,400)') + ); + + $this->config->set('system.images.cls.aspect_ratio', false); + + self::assertRegExp( + '/style="--aspect-ratio: 800\/400;"/', + $this->parsedown->text('![](sample-image.jpg?reset&resize=800,400&aspectRatio=true)') + ); + + } + + public function testRootImages(): void + { + $this->uri->initializeWithURL('http://testing.dev/')->init(); + + $defaults = [ + 'markdown' => [ + 'extra' => false, + 'auto_line_breaks' => false, + 'auto_url_links' => false, + 'escape_markup' => false, + 'special_chars' => ['>' => 'gt', '<' => 'lt'], + ], + 'images' => $this->config->get('system.images', []) + ]; + $page = $this->pages->find('/'); + $excerpts = new Excerpts($page, $defaults); + $this->parsedown = new Parsedown($excerpts); + + self::assertSame( + '

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

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

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

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

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

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

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

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

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

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

    Down a Level

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

    Peer Page

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

    With Query

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

    With Param

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

    With Anchor

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

    Peer Page

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

    Down a Level

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

    With Query

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

    With Param

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

    With Anchor

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

    Current Anchor

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

    Root Anchor

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

    Peer Anchor

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

    Peer Anchor 2

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

    Current Anchor

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

    Peer Anchor

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

    Peer Anchor 2

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

    Root Anchor

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

    cnn.com

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

    google.com

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

    complex url

    ', + $this->parsedown->text('[complex url](https://github.com/getgrav/grav/issues/new?title=[add-resource]%20New%20Plugin/Theme&body=Hello%20**There**)') + ); + } + + public function testExternalLinksSubDir(): void + { + $this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/item2/item2-2', '/subdir')->init(); + + self::assertSame( + '

    cnn.com

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

    google.com

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

    cnn.com

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

    google.com

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

    Current Anchor

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

    Root Anchor

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

    Peer Anchor

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

    Peer Anchor 2

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

    Current Anchor

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

    Peer Anchor

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

    Peer Anchor 2

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

    Root Anchor

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

    Peer Anchor

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

    Peer Anchor 2

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

    Current Anchor

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

    Root Anchor

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

    Peer Anchor

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

    Peer Anchor 2

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

    Current Anchor

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

    Root Anchor

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

    Peer Anchor

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

    Peer Anchor 2

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

    Current Anchor

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

    Root Anchor

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

    Up to Root Level

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

    Peer Page

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

    Down a Level

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

    Up a Level

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

    Up and Down

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

    Down a Level with Query

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

    Up a Level with Query

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

    Up and Down with Query

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

    Up and Down with Param

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

    Up and Down with Anchor

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

    Peer Page

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

    Down a Level

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

    Up a Level

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

    Up to Root Level

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

    Up and Down

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

    Down a Level with Query

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

    Up a Level with Query

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

    Up and Down with Query

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

    Up and Down with Param

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

    Up and Down with Anchor

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

    Peer Page

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

    Down a Level

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

    Up a Level

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

    Up to Root Level

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

    Up and Down

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

    Down a Level with Query

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

    Up a Level with Query

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

    Up and Down with Query

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

    Up and Down with Param

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

    Up and Down with Anchor

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

    Peer Page

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

    Down a Level

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

    Up a Level

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

    Up to Root Level

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

    Up and Down

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

    Down a Level with Query

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

    Up a Level with Query

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

    Up and Down with Query

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

    Up and Down with Param

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

    Up and Down with Anchor

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

    Up and Down with Param

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

    Peer Page

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

    Down a Level

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

    Up and Down

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

    Down a Level with Query

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

    Up and Down with Query

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

    Up and Down with Anchor

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

    Root

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

    Peer Page

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

    Down a Level

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

    Up a Level

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

    With Query

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

    With Param

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

    With Anchor

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

    Root

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

    Peer Page

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

    Down a Level

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

    Up a Level

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

    With Query

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

    With Param

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

    With Anchor

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

    Root

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

    Peer Page

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

    Down a Level

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

    Up a Level

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

    With Query

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

    With Param

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

    With Anchor

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

    mailto

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

    xmpp

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

    tel

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

    sms

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

    ts.example.com

    ', + $this->parsedown->text('[ts.example.com](rdp://ts.example.com)') + ); + } + + public function testSpecialProtocolsSubDir(): void + { + $this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/item2/item2-2', '/subdir')->init(); + + self::assertSame( + '

    mailto

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

    xmpp

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

    tel

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

    sms

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

    ts.example.com

    ', + $this->parsedown->text('[ts.example.com](rdp://ts.example.com)') + ); + } + + public function testSpecialProtocolsSubDirAbsoluteUrl(): void + { + $this->config->set('system.absolute_urls', true); + $this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/item2/item2-2', '/subdir')->init(); + + self::assertSame( + '

    mailto

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

    xmpp

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

    tel

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

    sms

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

    ts.example.com

    ', + $this->parsedown->text('[ts.example.com](rdp://ts.example.com)') + ); + } + + public function testReferenceLinks(): void + { + $this->uri->initializeWithURL('http://testing.dev/item2/item2-2')->init(); + + $sample = '[relative link][r_relative] + [r_relative]: ../item2-3#blah'; + self::assertSame( + '

    relative link

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

    absolute link

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

    external link

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

    Anchor Class

    ', + $this->parsedown->text('[Anchor Class](?classes=button#something)') + ); + self::assertSame( + '

    Relative Class

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

    Relative ID

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

    External

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

    Relative Noprocess

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

    Relative Target

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

    Relative Rel

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

    Relative Mixed

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

    Non Existent Page

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

    Existent File

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

    Non Existent File

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

    Non Existent Page

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

    Existent File

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

    Non Existent File

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

    Non Existent Page

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

    Existent File

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

    Non Existent File

    ', + $this->parsedown->text('[Non Existent File](missing-file.zip)') + ); + } + + + /** + * @param $string + * + * @return mixed + */ + private function stripLeadingWhitespace($string) + { + return preg_replace('/^\s*(.*)/', '', $string); + } + + private function setImagesDefaults($defaults) { + $defaults = [ + 'images' => [ + 'defaults' => $defaults + ], + ]; + $page = $this->pages->find('/item2/item2-2'); + $excerpts = new Excerpts($page, $defaults); + $this->parsedown = new Parsedown($excerpts); + } +} diff --git a/tests/unit/Grav/Common/Page/PagesTest.php b/tests/unit/Grav/Common/Page/PagesTest.php new file mode 100644 index 0000000..edff75b --- /dev/null +++ b/tests/unit/Grav/Common/Page/PagesTest.php @@ -0,0 +1,299 @@ +grav = $grav(); + $this->pages = $this->grav['pages']; + $this->grav['config']->set('system.home.alias', '/home'); + + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + + $locator->addPath('page', '', 'tests/fake/simple-site/user/pages', false); + $this->pages->init(); + } + + public function testBase(): void + { + self::assertSame('', $this->pages->base()); + $this->pages->base('/test'); + self::assertSame('/test', $this->pages->base()); + $this->pages->base(''); + self::assertSame($this->pages->base(), ''); + } + + public function testLastModified(): void + { + self::assertNull($this->pages->lastModified()); + $this->pages->lastModified('test'); + self::assertSame('test', $this->pages->lastModified()); + } + + public function testInstances(): void + { + self::assertIsArray($this->pages->instances()); + foreach ($this->pages->instances() as $instance) { + self::assertInstanceOf(PageInterface::class, $instance); + } + } + + public function testRoutes(): void + { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + $folder = $locator->findResource('tests://'); + + self::assertIsArray($this->pages->routes()); + self::assertSame($folder . '/fake/simple-site/user/pages/01.home', $this->pages->routes()['/']); + self::assertSame($folder . '/fake/simple-site/user/pages/01.home', $this->pages->routes()['/home']); + self::assertSame($folder . '/fake/simple-site/user/pages/02.blog', $this->pages->routes()['/blog']); + self::assertSame($folder . '/fake/simple-site/user/pages/02.blog/post-one', $this->pages->routes()['/blog/post-one']); + self::assertSame($folder . '/fake/simple-site/user/pages/02.blog/post-two', $this->pages->routes()['/blog/post-two']); + self::assertSame($folder . '/fake/simple-site/user/pages/03.about', $this->pages->routes()['/about']); + } + + public function testAddPage(): void + { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + $folder = $locator->findResource('tests://'); + + $path = $folder . '/fake/single-pages/01.simple-page/default.md'; + $aPage = new Page(); + $aPage->init(new \SplFileInfo($path)); + + $this->pages->addPage($aPage, '/new-page'); + + self::assertContains('/new-page', array_keys($this->pages->routes())); + self::assertSame($folder . '/fake/single-pages/01.simple-page', $this->pages->routes()['/new-page']); + } + + public function testSort(): void + { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + $folder = $locator->findResource('tests://'); + + $aPage = $this->pages->find('/blog'); + $subPagesSorted = $this->pages->sort($aPage); + + self::assertIsArray($subPagesSorted); + self::assertCount(2, $subPagesSorted); + + self::assertSame($folder . '/fake/simple-site/user/pages/02.blog/post-one', array_keys($subPagesSorted)[0]); + self::assertSame($folder . '/fake/simple-site/user/pages/02.blog/post-two', array_keys($subPagesSorted)[1]); + + self::assertContains($folder . '/fake/simple-site/user/pages/02.blog/post-one', array_keys($subPagesSorted)); + self::assertContains($folder . '/fake/simple-site/user/pages/02.blog/post-two', array_keys($subPagesSorted)); + + self::assertSame(['slug' => 'post-one'], $subPagesSorted[$folder . '/fake/simple-site/user/pages/02.blog/post-one']); + self::assertSame(['slug' => 'post-two'], $subPagesSorted[$folder . '/fake/simple-site/user/pages/02.blog/post-two']); + + $subPagesSorted = $this->pages->sort($aPage, null, 'desc'); + + self::assertIsArray($subPagesSorted); + self::assertCount(2, $subPagesSorted); + + self::assertSame($folder . '/fake/simple-site/user/pages/02.blog/post-two', array_keys($subPagesSorted)[0]); + self::assertSame($folder . '/fake/simple-site/user/pages/02.blog/post-one', array_keys($subPagesSorted)[1]); + + self::assertContains($folder . '/fake/simple-site/user/pages/02.blog/post-one', array_keys($subPagesSorted)); + self::assertContains($folder . '/fake/simple-site/user/pages/02.blog/post-two', array_keys($subPagesSorted)); + + self::assertSame(['slug' => 'post-one'], $subPagesSorted[$folder . '/fake/simple-site/user/pages/02.blog/post-one']); + self::assertSame(['slug' => 'post-two'], $subPagesSorted[$folder . '/fake/simple-site/user/pages/02.blog/post-two']); + } + + public function testSortCollection(): void + { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + $folder = $locator->findResource('tests://'); + + $aPage = $this->pages->find('/blog'); + $subPagesSorted = $this->pages->sortCollection($aPage->children(), $aPage->orderBy()); + + self::assertIsArray($subPagesSorted); + self::assertCount(2, $subPagesSorted); + + self::assertSame($folder . '/fake/simple-site/user/pages/02.blog/post-one', array_keys($subPagesSorted)[0]); + self::assertSame($folder . '/fake/simple-site/user/pages/02.blog/post-two', array_keys($subPagesSorted)[1]); + + self::assertContains($folder . '/fake/simple-site/user/pages/02.blog/post-one', array_keys($subPagesSorted)); + self::assertContains($folder . '/fake/simple-site/user/pages/02.blog/post-two', array_keys($subPagesSorted)); + + self::assertSame(['slug' => 'post-one'], $subPagesSorted[$folder . '/fake/simple-site/user/pages/02.blog/post-one']); + self::assertSame(['slug' => 'post-two'], $subPagesSorted[$folder . '/fake/simple-site/user/pages/02.blog/post-two']); + + $subPagesSorted = $this->pages->sortCollection($aPage->children(), $aPage->orderBy(), 'desc'); + + self::assertIsArray($subPagesSorted); + self::assertCount(2, $subPagesSorted); + + self::assertSame($folder . '/fake/simple-site/user/pages/02.blog/post-two', array_keys($subPagesSorted)[0]); + self::assertSame($folder . '/fake/simple-site/user/pages/02.blog/post-one', array_keys($subPagesSorted)[1]); + + self::assertContains($folder . '/fake/simple-site/user/pages/02.blog/post-one', array_keys($subPagesSorted)); + self::assertContains($folder . '/fake/simple-site/user/pages/02.blog/post-two', array_keys($subPagesSorted)); + + self::assertSame(['slug' => 'post-one'], $subPagesSorted[$folder . '/fake/simple-site/user/pages/02.blog/post-one']); + self::assertSame(['slug' => 'post-two'], $subPagesSorted[$folder . '/fake/simple-site/user/pages/02.blog/post-two']); + } + + public function testGet(): void + { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + $folder = $locator->findResource('tests://'); + + //Page existing + $aPage = $this->pages->get($folder . '/fake/simple-site/user/pages/03.about'); + self::assertInstanceOf(PageInterface::class, $aPage); + + //Page not existing + $anotherPage = $this->pages->get($folder . '/fake/simple-site/user/pages/03.non-existing'); + self::assertNull($anotherPage); + } + + public function testChildren(): void + { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + $folder = $locator->findResource('tests://'); + + //Page existing + $children = $this->pages->children($folder . '/fake/simple-site/user/pages/02.blog'); + self::assertInstanceOf('Grav\Common\Page\Collection', $children); + + //Page not existing + $children = $this->pages->children($folder . '/fake/whatever/non-existing'); + self::assertSame([], $children->toArray()); + } + + public function testDispatch(): void + { + $aPage = $this->pages->dispatch('/blog'); + self::assertInstanceOf(PageInterface::class, $aPage); + + $aPage = $this->pages->dispatch('/about'); + self::assertInstanceOf(PageInterface::class, $aPage); + + $aPage = $this->pages->dispatch('/blog/post-one'); + self::assertInstanceOf(PageInterface::class, $aPage); + + //Page not existing + $aPage = $this->pages->dispatch('/non-existing'); + self::assertNull($aPage); + } + + public function testRoot(): void + { + $root = $this->pages->root(); + self::assertInstanceOf(PageInterface::class, $root); + self::assertSame('pages', $root->folder()); + } + + public function testBlueprints(): void + { + } + + public function testAll() + { + self::assertIsObject($this->pages->all()); + self::assertIsArray($this->pages->all()->toArray()); + foreach ($this->pages->all() as $page) { + self::assertInstanceOf(PageInterface::class, $page); + } + } + + public function testGetList(): void + { + $list = $this->pages->getList(); + self::assertIsArray($list); + self::assertSame('—-▸ Home', $list['/']); + self::assertSame('—-▸ Blog', $list['/blog']); + } + + public function testTranslatedLanguages(): void + { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + $folder = $locator->findResource('tests://'); + + $page = $this->pages->get($folder . '/fake/simple-site/user/pages/04.page-translated'); + $this->assertInstanceOf(PageInterface::class, $page); + $translatedLanguages = $page->translatedLanguages(); + $this->assertIsArray($translatedLanguages); + $this->assertSame(["en" => "/page-translated", "fr" => "/page-translated"], $translatedLanguages); + } + + public function testLongPathTranslatedLanguages(): void + { + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + $folder = $locator->findResource('tests://'); + $page = $this->pages->get($folder . '/fake/simple-site/user/pages/05.translatedlong/part2'); + $this->assertInstanceOf(PageInterface::class, $page); + $translatedLanguages = $page->translatedLanguages(); + $this->assertIsArray($translatedLanguages); + $this->assertSame(["en" => "/translatedlong/part2", "fr" => "/translatedlong/part2"], $translatedLanguages); + } + + public function testGetTypes(): void + { + } + + public function testTypes(): void + { + } + + public function testModularTypes(): void + { + } + + public function testPageTypes(): void + { + } + + public function testAccessLevels(): void + { + } + + public function testParents(): void + { + } + + public function testParentsRawRoutes(): void + { + } + + public function testGetHomeRoute(): void + { + } + + public function testResetPages(): void + { + } +} diff --git a/tests/unit/Grav/Common/Twig/Extensions/GravExtensionTest.php b/tests/unit/Grav/Common/Twig/Extensions/GravExtensionTest.php new file mode 100644 index 0000000..2adb023 --- /dev/null +++ b/tests/unit/Grav/Common/Twig/Extensions/GravExtensionTest.php @@ -0,0 +1,202 @@ +grav = Fixtures::get('grav'); + $this->twig_ext = new GravExtension(); + } + + public function testInflectorFilter(): void + { + self::assertSame('people', $this->twig_ext->inflectorFilter('plural', 'person')); + self::assertSame('shoe', $this->twig_ext->inflectorFilter('singular', 'shoes')); + self::assertSame('Welcome Page', $this->twig_ext->inflectorFilter('title', 'welcome page')); + self::assertSame('SendEmail', $this->twig_ext->inflectorFilter('camel', 'send_email')); + self::assertSame('camel_cased', $this->twig_ext->inflectorFilter('underscor', 'CamelCased')); + self::assertSame('something-text', $this->twig_ext->inflectorFilter('hyphen', 'Something Text')); + self::assertSame('Something text to read', $this->twig_ext->inflectorFilter('human', 'something_text_to_read')); + self::assertSame(5, $this->twig_ext->inflectorFilter('month', '175')); + self::assertSame('10th', $this->twig_ext->inflectorFilter('ordinal', '10')); + } + + public function testMd5Filter(): void + { + self::assertSame(md5('grav'), $this->twig_ext->md5Filter('grav')); + self::assertSame(md5('devs@getgrav.org'), $this->twig_ext->md5Filter('devs@getgrav.org')); + } + + public function testKsortFilter(): void + { + $object = array("name"=>"Bob","age"=>8,"colour"=>"red"); + self::assertSame(array("age"=>8,"colour"=>"red","name"=>"Bob"), $this->twig_ext->ksortFilter($object)); + } + + public function testContainsFilter(): void + { + self::assertTrue($this->twig_ext->containsFilter('grav', 'grav')); + self::assertTrue($this->twig_ext->containsFilter('So, I found this new cms, called grav, and it\'s pretty awesome guys', 'grav')); + } + + public function testNicetimeFilter(): void + { + $now = time(); + $threeMinutes = time() - (60*3); + $threeHours = time() - (60*60*3); + $threeDays = time() - (60*60*24*3); + $threeMonths = time() - (60*60*24*30*3); + $threeYears = time() - (60*60*24*365*3); + $measures = ['minutes','hours','days','months','years']; + + self::assertSame('No date provided', $this->twig_ext->nicetimeFunc(null)); + + for ($i=0; $itwig_ext->nicetimeFunc($$time)); + } + } + + public function testRandomizeFilter(): void + { + $array = [1,2,3,4,5]; + self::assertContains(2, $this->twig_ext->randomizeFilter($array)); + self::assertSame($array, $this->twig_ext->randomizeFilter($array, 5)); + self::assertSame($array[0], $this->twig_ext->randomizeFilter($array, 1)[0]); + self::assertSame($array[3], $this->twig_ext->randomizeFilter($array, 4)[3]); + self::assertSame($array[1], $this->twig_ext->randomizeFilter($array, 4)[1]); + } + + public function testModulusFilter(): void + { + self::assertSame(3, $this->twig_ext->modulusFilter(3, 4)); + self::assertSame(1, $this->twig_ext->modulusFilter(11, 2)); + self::assertSame(0, $this->twig_ext->modulusFilter(10, 2)); + self::assertSame(2, $this->twig_ext->modulusFilter(10, 4)); + } + + public function testAbsoluteUrlFilter(): void + { + } + + public function testMarkdownFilter(): void + { + } + + public function testStartsWithFilter(): void + { + } + + public function testEndsWithFilter(): void + { + } + + public function testDefinedDefaultFilter(): void + { + } + + public function testRtrimFilter(): void + { + } + + public function testLtrimFilter(): void + { + } + + public function testRepeatFunc(): void + { + } + + public function testRegexReplace(): void + { + } + + public function testUrlFunc(): void + { + } + + public function testEvaluateFunc(): void + { + } + + public function testDump(): void + { + } + + public function testGistFunc(): void + { + } + + public function testRandomStringFunc(): void + { + } + + public function testPadFilter(): void + { + } + + public function testArrayFunc(): void + { + self::assertSame( + 'this is my text', + $this->twig_ext->regexReplace('

    this is my text

    ', '(<\/?p>)', '') + ); + self::assertSame( + 'this is my text', + $this->twig_ext->regexReplace('

    this is my text

    ', ['(

    )','(<\/p>)'], ['','']) + ); + } + + public function testArrayKeyValue(): void + { + self::assertSame( + ['meat' => 'steak'], + $this->twig_ext->arrayKeyValueFunc('meat', 'steak') + ); + self::assertSame( + ['fruit' => 'apple', 'meat' => 'steak'], + $this->twig_ext->arrayKeyValueFunc('meat', 'steak', ['fruit' => 'apple']) + ); + } + + public function stringFunc(): void + { + } + + public function testRangeFunc(): void + { + $hundred = []; + for ($i = 0; $i <= 100; $i++) { + $hundred[] = $i; + } + + + self::assertSame([0], $this->twig_ext->rangeFunc(0, 0)); + self::assertSame([0, 1, 2], $this->twig_ext->rangeFunc(0, 2)); + + self::assertSame([0, 5, 10, 15], $this->twig_ext->rangeFunc(0, 16, 5)); + + // default (min 0, max 100, step 1) + self::assertSame($hundred, $this->twig_ext->rangeFunc()); + + // 95 items, starting from 5, (min 5, max 100, step 1) + self::assertSame(array_slice($hundred, 5), $this->twig_ext->rangeFunc(5)); + + // reversed range + self::assertSame(array_reverse($hundred), $this->twig_ext->rangeFunc(100, 0)); + self::assertSame([4, 2, 0], $this->twig_ext->rangeFunc(4, 0, 2)); + } +} diff --git a/tests/unit/Grav/Common/UriTest.php b/tests/unit/Grav/Common/UriTest.php new file mode 100644 index 0000000..c36ce52 --- /dev/null +++ b/tests/unit/Grav/Common/UriTest.php @@ -0,0 +1,1178 @@ + [ + 'scheme' => '', + 'user' => null, + 'password' => null, + 'host' => null, + 'port' => null, + 'path' => '/path', + 'query' => '', + 'fragment' => null, + + 'route' => '/path', + 'paths' => ['path'], + 'params' => null, + 'url' => '/path', + 'environment' => 'unknown', + 'basename' => 'path', + 'base' => '', + 'currentPage' => 1, + 'rootUrl' => '', + 'extension' => null, + 'addNonce' => '/path/nonce:{{nonce}}', + ], + '//localhost/' => [ + 'scheme' => '//', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => null, + 'path' => '/', + 'query' => '', + 'fragment' => null, + + 'route' => '/', + 'paths' => [], + 'params' => null, + 'url' => '/', + 'environment' => 'localhost', + 'basename' => '', + 'base' => '//localhost', + 'currentPage' => 1, + 'rootUrl' => '//localhost', + 'extension' => null, + 'addNonce' => '//localhost/nonce:{{nonce}}', + ], + 'http://localhost/' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 80, + 'path' => '/', + 'query' => '', + 'fragment' => null, + + 'route' => '/', + 'paths' => [], + 'params' => null, + 'url' => '/', + 'environment' => 'localhost', + 'basename' => '', + 'base' => 'http://localhost', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost', + 'extension' => null, + 'addNonce' => 'http://localhost/nonce:{{nonce}}', + ], + 'http://127.0.0.1/' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => '127.0.0.1', + 'port' => 80, + 'path' => '/', + 'query' => '', + 'fragment' => null, + + 'route' => '/', + 'paths' => [], + 'params' => null, + 'url' => '/', + 'environment' => 'localhost', + 'basename' => '', + 'base' => 'http://127.0.0.1', + 'currentPage' => 1, + 'rootUrl' => 'http://127.0.0.1', + 'extension' => null, + 'addNonce' => 'http://127.0.0.1/nonce:{{nonce}}', + ], + 'https://localhost/' => [ + 'scheme' => 'https://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 443, + 'path' => '/', + 'query' => '', + 'fragment' => null, + + 'route' => '/', + 'paths' => [], + 'params' => null, + 'url' => '/', + 'environment' => 'localhost', + 'basename' => '', + 'base' => 'https://localhost', + 'currentPage' => 1, + 'rootUrl' => 'https://localhost', + 'extension' => null, + 'addNonce' => 'https://localhost/nonce:{{nonce}}', + ], + 'http://localhost:8080/grav/it/ueper' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 8080, + 'path' => '/grav/it/ueper', + 'query' => '', + 'fragment' => null, + + 'route' => '/grav/it/ueper', + 'paths' => ['grav', 'it', 'ueper'], + 'params' => null, + 'url' => '/grav/it/ueper', + 'environment' => 'localhost', + 'basename' => 'ueper', + 'base' => 'http://localhost:8080', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost:8080', + 'extension' => null, + 'addNonce' => 'http://localhost:8080/grav/it/ueper/nonce:{{nonce}}', + ], + 'http://localhost:8080/grav/it/ueper:xxx' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 8080, + 'path' => '/grav/it', + 'query' => '', + 'fragment' => null, + + 'route' => '/grav/it', + 'paths' => ['grav', 'it'], + 'params' => '/ueper:xxx', + 'url' => '/grav/it', + 'environment' => 'localhost', + 'basename' => 'it', + 'base' => 'http://localhost:8080', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost:8080', + 'extension' => null, + 'addNonce' => 'http://localhost:8080/grav/it/ueper:xxx/nonce:{{nonce}}', + ], + 'http://localhost:8080/grav/it/ueper:xxx/page:/test:yyy' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 8080, + 'path' => '/grav/it', + 'query' => '', + 'fragment' => null, + + 'route' => '/grav/it', + 'paths' => ['grav', 'it'], + 'params' => '/ueper:xxx/page:/test:yyy', + 'url' => '/grav/it', + 'environment' => 'localhost', + 'basename' => 'it', + 'base' => 'http://localhost:8080', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost:8080', + 'extension' => null, + 'addNonce' => 'http://localhost:8080/grav/it/ueper:xxx/page:/test:yyy/nonce:{{nonce}}', + ], + 'http://localhost:8080/grav/it/ueper?test=x' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 8080, + 'path' => '/grav/it/ueper', + 'query' => 'test=x', + 'fragment' => null, + + 'route' => '/grav/it/ueper', + 'paths' => ['grav', 'it', 'ueper'], + 'params' => null, + 'url' => '/grav/it/ueper', + 'environment' => 'localhost', + 'basename' => 'ueper', + 'base' => 'http://localhost:8080', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost:8080', + 'extension' => null, + 'addNonce' => 'http://localhost:8080/grav/it/ueper/nonce:{{nonce}}?test=x', + ], + 'http://localhost:80/grav/it/ueper?test=x' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 80, + 'path' => '/grav/it/ueper', + 'query' => 'test=x', + 'fragment' => null, + + 'route' => '/grav/it/ueper', + 'paths' => ['grav', 'it', 'ueper'], + 'params' => null, + 'url' => '/grav/it/ueper', + 'environment' => 'localhost', + 'basename' => 'ueper', + 'base' => 'http://localhost:80', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost:80', + 'extension' => null, + 'addNonce' => 'http://localhost:80/grav/it/ueper/nonce:{{nonce}}?test=x', + ], + 'http://localhost/grav/it/ueper?test=x' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 80, + 'path' => '/grav/it/ueper', + 'query' => 'test=x', + 'fragment' => null, + + 'route' => '/grav/it/ueper', + 'paths' => ['grav', 'it', 'ueper'], + 'params' => null, + 'url' => '/grav/it/ueper', + 'environment' => 'localhost', + 'basename' => 'ueper', + 'base' => 'http://localhost', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost', + 'extension' => null, + 'addNonce' => 'http://localhost/grav/it/ueper/nonce:{{nonce}}?test=x', + ], + 'http://grav/grav/it/ueper' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'grav', + 'port' => 80, + 'path' => '/grav/it/ueper', + 'query' => '', + 'fragment' => null, + + 'route' => '/grav/it/ueper', + 'paths' => ['grav', 'it', 'ueper'], + 'params' => null, + 'url' => '/grav/it/ueper', + 'environment' => 'grav', + 'basename' => 'ueper', + 'base' => 'http://grav', + 'currentPage' => 1, + 'rootUrl' => 'http://grav', + 'extension' => null, + 'addNonce' => 'http://grav/grav/it/ueper/nonce:{{nonce}}', + ], + 'https://username:password@api.getgrav.com:4040/v1/post/128/page:x/?all=1' => [ + 'scheme' => 'https://', + 'user' => 'username', + 'password' => 'password', + 'host' => 'api.getgrav.com', + 'port' => 4040, + 'path' => '/v1/post/128/', // FIXME <- + 'query' => 'all=1', + 'fragment' => null, + + 'route' => '/v1/post/128', + 'paths' => ['v1', 'post', '128'], + 'params' => '/page:x', + 'url' => '/v1/post/128', + 'environment' => 'api.getgrav.com', + 'basename' => '128', + 'base' => 'https://api.getgrav.com:4040', + 'currentPage' => 1, + 'rootUrl' => 'https://api.getgrav.com:4040', + 'extension' => null, + 'addNonce' => 'https://username:password@api.getgrav.com:4040/v1/post/128/page:x/nonce:{{nonce}}?all=1', + 'toOriginalString' => 'https://username:password@api.getgrav.com:4040/v1/post/128/page:x?all=1' + ], + 'https://google.com:443/' => [ + 'scheme' => 'https://', + 'user' => null, + 'password' => null, + 'host' => 'google.com', + 'port' => 443, + 'path' => '/', + 'query' => '', + 'fragment' => null, + + 'route' => '/', + 'paths' => [], + 'params' => null, + 'url' => '/', + 'environment' => 'google.com', + 'basename' => '', + 'base' => 'https://google.com:443', + 'currentPage' => 1, + 'rootUrl' => 'https://google.com:443', + 'extension' => null, + 'addNonce' => 'https://google.com:443/nonce:{{nonce}}', + ], + // Path tests. + 'http://localhost:8080/a/b/c/d' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 8080, + 'path' => '/a/b/c/d', + 'query' => '', + 'fragment' => null, + + 'route' => '/a/b/c/d', + 'paths' => ['a', 'b', 'c', 'd'], + 'params' => null, + 'url' => '/a/b/c/d', + 'environment' => 'localhost', + 'basename' => 'd', + 'base' => 'http://localhost:8080', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost:8080', + 'extension' => null, + 'addNonce' => 'http://localhost:8080/a/b/c/d/nonce:{{nonce}}', + ], + 'http://localhost:8080/a/b/c/d/e/f/a/b/c/d/e/f/a/b/c/d/e/f' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 8080, + 'path' => '/a/b/c/d/e/f/a/b/c/d/e/f/a/b/c/d/e/f', + 'query' => '', + 'fragment' => null, + + 'route' => '/a/b/c/d/e/f/a/b/c/d/e/f/a/b/c/d/e/f', + 'paths' => ['a', 'b', 'c', 'd', 'e', 'f', 'a', 'b', 'c', 'd', 'e', 'f', 'a', 'b', 'c', 'd', 'e', 'f'], + 'params' => null, + 'url' => '/a/b/c/d/e/f/a/b/c/d/e/f/a/b/c/d/e/f', + 'environment' => 'localhost', + 'basename' => 'f', + 'base' => 'http://localhost:8080', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost:8080', + 'extension' => null, + 'addNonce' => 'http://localhost:8080/a/b/c/d/e/f/a/b/c/d/e/f/a/b/c/d/e/f/nonce:{{nonce}}', + ], + 'http://localhost/this is the path/my page' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 80, + 'path' => '/this%20is%20the%20path/my%20page', + 'query' => '', + 'fragment' => null, + + 'route' => '/this%20is%20the%20path/my%20page', + 'paths' => ['this%20is%20the%20path', 'my%20page'], + 'params' => null, + 'url' => '/this%20is%20the%20path/my%20page', + 'environment' => 'localhost', + 'basename' => 'my%20page', + 'base' => 'http://localhost', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost', + 'extension' => null, + 'addNonce' => 'http://localhost/this%20is%20the%20path/my%20page/nonce:{{nonce}}', + 'toOriginalString' => 'http://localhost/this%20is%20the%20path/my%20page' + ], + 'http://localhost/pölöpölö/päläpälä' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 80, + 'path' => '/p%C3%B6l%C3%B6p%C3%B6l%C3%B6/p%C3%A4l%C3%A4p%C3%A4l%C3%A4', + 'query' => '', + 'fragment' => null, + + 'route' => '/p%C3%B6l%C3%B6p%C3%B6l%C3%B6/p%C3%A4l%C3%A4p%C3%A4l%C3%A4', + 'paths' => ['p%C3%B6l%C3%B6p%C3%B6l%C3%B6', 'p%C3%A4l%C3%A4p%C3%A4l%C3%A4'], + 'params' => null, + 'url' => '/p%C3%B6l%C3%B6p%C3%B6l%C3%B6/p%C3%A4l%C3%A4p%C3%A4l%C3%A4', + 'environment' => 'localhost', + 'basename' => 'p%C3%A4l%C3%A4p%C3%A4l%C3%A4', + 'base' => 'http://localhost', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost', + 'extension' => null, + 'addNonce' => 'http://localhost/p%C3%B6l%C3%B6p%C3%B6l%C3%B6/p%C3%A4l%C3%A4p%C3%A4l%C3%A4/nonce:{{nonce}}', + 'toOriginalString' => 'http://localhost/p%C3%B6l%C3%B6p%C3%B6l%C3%B6/p%C3%A4l%C3%A4p%C3%A4l%C3%A4' + ], + // Query params tests. + 'http://localhost:8080/grav/it/ueper?test=x&test2=y' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 8080, + 'path' => '/grav/it/ueper', + 'query' => 'test=x&test2=y', + 'fragment' => null, + + 'route' => '/grav/it/ueper', + 'paths' => ['grav', 'it', 'ueper'], + 'params' => null, + 'url' => '/grav/it/ueper', + 'environment' => 'localhost', + 'basename' => 'ueper', + 'base' => 'http://localhost:8080', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost:8080', + 'extension' => null, + 'addNonce' => 'http://localhost:8080/grav/it/ueper/nonce:{{nonce}}?test=x&test2=y', + ], + 'http://localhost:8080/grav/it/ueper?test=x&test2=y&test3=x&test4=y' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 8080, + 'path' => '/grav/it/ueper', + 'query' => 'test=x&test2=y&test3=x&test4=y', + 'fragment' => null, + + 'route' => '/grav/it/ueper', + 'paths' => ['grav', 'it', 'ueper'], + 'params' => null, + 'url' => '/grav/it/ueper', + 'environment' => 'localhost', + 'basename' => 'ueper', + 'base' => 'http://localhost:8080', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost:8080', + 'extension' => null, + 'addNonce' => 'http://localhost:8080/grav/it/ueper/nonce:{{nonce}}?test=x&test2=y&test3=x&test4=y', + ], + 'http://localhost:8080/grav/it/ueper?test=x&test2=y&test3=x&test4=y/test' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 8080, + 'path' => '/grav/it/ueper', + 'query' => 'test=x&test2=y&test3=x&test4=y%2Ftest', + 'fragment' => null, + + 'route' => '/grav/it/ueper', + 'paths' => ['grav', 'it', 'ueper'], + 'params' => null, + 'url' => '/grav/it/ueper', + 'environment' => 'localhost', + 'basename' => 'ueper', + 'base' => 'http://localhost:8080', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost:8080', + 'extension' => null, + 'addNonce' => 'http://localhost:8080/grav/it/ueper/nonce:{{nonce}}?test=x&test2=y&test3=x&test4=y/test', + ], + // Port tests. + 'http://localhost/a-page' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 80, + 'path' => '/a-page', + 'query' => '', + 'fragment' => null, + + 'route' => '/a-page', + 'paths' => ['a-page'], + 'params' => null, + 'url' => '/a-page', + 'environment' => 'localhost', + 'basename' => 'a-page', + 'base' => 'http://localhost', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost', + 'extension' => null, + 'addNonce' => 'http://localhost/a-page/nonce:{{nonce}}', + ], + 'http://localhost:8080/a-page' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 8080, + 'path' => '/a-page', + 'query' => '', + 'fragment' => null, + + 'route' => '/a-page', + 'paths' => ['a-page'], + 'params' => null, + 'url' => '/a-page', + 'environment' => 'localhost', + 'basename' => 'a-page', + 'base' => 'http://localhost:8080', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost:8080', + 'extension' => null, + 'addNonce' => 'http://localhost:8080/a-page/nonce:{{nonce}}', + ], + 'http://localhost:443/a-page' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 443, + 'path' => '/a-page', + 'query' => '', + 'fragment' => null, + + 'route' => '/a-page', + 'paths' => ['a-page'], + 'params' => null, + 'url' => '/a-page', + 'environment' => 'localhost', + 'basename' => 'a-page', + 'base' => 'http://localhost:443', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost:443', + 'extension' => null, + 'addNonce' => 'http://localhost:443/a-page/nonce:{{nonce}}', + ], + // Extension tests. + 'http://localhost/a-page.html' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 80, + 'path' => '/a-page', + 'query' => '', + 'fragment' => null, + + 'route' => '/a-page', + 'paths' => ['a-page'], + 'params' => null, + 'url' => '/a-page', + 'environment' => 'localhost', + 'basename' => 'a-page.html', + 'base' => 'http://localhost', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost', + 'extension' => 'html', + 'addNonce' => 'http://localhost/a-page.html/nonce:{{nonce}}', + 'toOriginalString' => 'http://localhost/a-page.html', + ], + 'http://localhost/a-page.json' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 80, + 'path' => '/a-page', + 'query' => '', + 'fragment' => null, + + 'route' => '/a-page', + 'paths' => ['a-page'], + 'params' => null, + 'url' => '/a-page', + 'environment' => 'localhost', + 'basename' => 'a-page.json', + 'base' => 'http://localhost', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost', + 'extension' => 'json', + 'addNonce' => 'http://localhost/a-page.json/nonce:{{nonce}}', + 'toOriginalString' => 'http://localhost/a-page.json', + ], + 'http://localhost/admin/ajax.json/task:getnewsfeed' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 80, + 'path' => '/admin/ajax', + 'query' => '', + 'fragment' => null, + + 'route' => '/admin/ajax', + 'paths' => ['admin', 'ajax'], + 'params' => '/task:getnewsfeed', + 'url' => '/admin/ajax', + 'environment' => 'localhost', + 'basename' => 'ajax.json', + 'base' => 'http://localhost', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost', + 'extension' => 'json', + 'addNonce' => 'http://localhost/admin/ajax.json/task:getnewsfeed/nonce:{{nonce}}', + 'toOriginalString' => 'http://localhost/admin/ajax.json/task:getnewsfeed', + ], + 'http://localhost/grav/admin/media.json/route:L1VzZXJzL3JodWsvd29ya3NwYWNlL2dyYXYtZGVtby1zYW1wbGVyL3VzZXIvYXNzZXRzL3FRMXB4Vk1ERTNJZzh5Ni5qcGc=/task:removeFileFromBlueprint/proute:/blueprint:Y29uZmlnL2RldGFpbHM=/type:config/field:deep.nested.custom_file/path:dXNlci9hc3NldHMvcVExcHhWTURFM0lnOHk2LmpwZw==' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 80, + 'path' => '/grav/admin/media', + 'query' => '', + 'fragment' => null, + + 'route' => '/grav/admin/media', + 'paths' => ['grav','admin','media'], + 'params' => '/route:L1VzZXJzL3JodWsvd29ya3NwYWNlL2dyYXYtZGVtby1zYW1wbGVyL3VzZXIvYXNzZXRzL3FRMXB4Vk1ERTNJZzh5Ni5qcGc=/task:removeFileFromBlueprint/proute:/blueprint:Y29uZmlnL2RldGFpbHM=/type:config/field:deep.nested.custom_file/path:dXNlci9hc3NldHMvcVExcHhWTURFM0lnOHk2LmpwZw==', + 'url' => '/grav/admin/media', + 'environment' => 'localhost', + 'basename' => 'media.json', + 'base' => 'http://localhost', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost', + 'extension' => 'json', + 'addNonce' => 'http://localhost/grav/admin/media.json/route:L1VzZXJzL3JodWsvd29ya3NwYWNlL2dyYXYtZGVtby1zYW1wbGVyL3VzZXIvYXNzZXRzL3FRMXB4Vk1ERTNJZzh5Ni5qcGc=/task:removeFileFromBlueprint/proute:/blueprint:Y29uZmlnL2RldGFpbHM=/type:config/field:deep.nested.custom_file/path:dXNlci9hc3NldHMvcVExcHhWTURFM0lnOHk2LmpwZw==/nonce:{{nonce}}', + 'toOriginalString' => 'http://localhost/grav/admin/media.json/route:L1VzZXJzL3JodWsvd29ya3NwYWNlL2dyYXYtZGVtby1zYW1wbGVyL3VzZXIvYXNzZXRzL3FRMXB4Vk1ERTNJZzh5Ni5qcGc=/task:removeFileFromBlueprint/proute:/blueprint:Y29uZmlnL2RldGFpbHM=/type:config/field:deep.nested.custom_file/path:dXNlci9hc3NldHMvcVExcHhWTURFM0lnOHk2LmpwZw==', + ], + 'http://localhost/a-page.foo' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 80, + 'path' => '/a-page.foo', + 'query' => '', + 'fragment' => null, + + 'route' => '/a-page.foo', + 'paths' => ['a-page.foo'], + 'params' => null, + 'url' => '/a-page.foo', + 'environment' => 'localhost', + 'basename' => 'a-page.foo', + 'base' => 'http://localhost', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost', + 'extension' => 'foo', + 'addNonce' => 'http://localhost/a-page.foo/nonce:{{nonce}}', + 'toOriginalString' => 'http://localhost/a-page.foo' + ], + // Fragment tests. + 'http://localhost:8080/a/b/c#my-fragment' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 8080, + 'path' => '/a/b/c', + 'query' => '', + 'fragment' => 'my-fragment', + + 'route' => '/a/b/c', + 'paths' => ['a', 'b', 'c'], + 'params' => null, + 'url' => '/a/b/c', + 'environment' => 'localhost', + 'basename' => 'c', + 'base' => 'http://localhost:8080', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost:8080', + 'extension' => null, + 'addNonce' => 'http://localhost:8080/a/b/c/nonce:{{nonce}}#my-fragment', + ], + // Attacks. + '">://localhost' => [ + 'scheme' => '', + 'user' => null, + 'password' => null, + 'host' => null, + 'port' => null, + 'path' => '/localhost', + 'query' => '', + 'fragment' => null, + + 'route' => '/localhost', + 'paths' => ['localhost'], + 'params' => '/script%3E:', + 'url' => '/localhost', + 'environment' => 'unknown', + 'basename' => 'localhost', + 'base' => '', + 'currentPage' => 1, + 'rootUrl' => '', + 'extension' => null, + //'addNonce' => '%22%3E%3Cscript%3Ealert%3C/localhost/script%3E:/nonce:{{nonce}}', // FIXME <- + 'toOriginalString' => '/localhost/script%3E:' // FIXME <- + ], + 'http://">' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'unknown', + 'port' => 80, + 'path' => '/script%3E', + 'query' => '', + 'fragment' => null, + + 'route' => '/script%3E', + 'paths' => ['script%3E'], + 'params' => null, + 'url' => '/script%3E', + 'environment' => 'unknown', + 'basename' => 'script%3E', + 'base' => 'http://unknown', + 'currentPage' => 1, + 'rootUrl' => 'http://unknown', + 'extension' => null, + 'addNonce' => 'http://unknown/script%3E/nonce:{{nonce}}', + 'toOriginalString' => 'http://unknown/script%3E' + ], + 'http://localhost/">' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 80, + 'path' => '/%22%3E%3Cscript%3Ealert%3C/script%3E', + 'query' => '', + 'fragment' => null, + + 'route' => '/%22%3E%3Cscript%3Ealert%3C/script%3E', + 'paths' => ['%22%3E%3Cscript%3Ealert%3C', 'script%3E'], + 'params' => null, + 'url' => '/%22%3E%3Cscript%3Ealert%3C/script%3E', + 'environment' => 'localhost', + 'basename' => 'script%3E', + 'base' => 'http://localhost', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost', + 'extension' => null, + 'addNonce' => 'http://localhost/%22%3E%3Cscript%3Ealert%3C/script%3E/nonce:{{nonce}}', + 'toOriginalString' => 'http://localhost/%22%3E%3Cscript%3Ealert%3C/script%3E' + ], + 'http://localhost/something/p1:foo/p2:">' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 80, + 'path' => '/something/script%3E', + 'query' => '', + 'fragment' => null, + + 'route' => '/something/script%3E', + 'paths' => ['something', 'script%3E'], + 'params' => '/p1:foo/p2:%22%3E%3Cscript%3Ealert%3C', + 'url' => '/something/script%3E', + 'environment' => 'localhost', + 'basename' => 'script%3E', + 'base' => 'http://localhost', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost', + 'extension' => null, + //'addNonce' => 'http://localhost/something/script%3E/p1:foo/p2:%22%3E%3Cscript%3Ealert%3C/nonce:{{nonce}}', // FIXME <- + 'toOriginalString' => 'http://localhost/something/script%3E/p1:foo/p2:%22%3E%3Cscript%3Ealert%3C' + ], + 'http://localhost/something?p=">' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 80, + 'path' => '/something', + 'query' => 'p=%22%3E%3Cscript%3Ealert%3C%2Fscript%3E', + 'fragment' => null, + + 'route' => '/something', + 'paths' => ['something'], + 'params' => null, + 'url' => '/something', + 'environment' => 'localhost', + 'basename' => 'something', + 'base' => 'http://localhost', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost', + 'extension' => null, + 'addNonce' => 'http://localhost/something/nonce:{{nonce}}?p=%22%3E%3Cscript%3Ealert%3C/script%3E', + 'toOriginalString' => 'http://localhost/something?p=%22%3E%3Cscript%3Ealert%3C/script%3E' + ], + 'http://localhost/something#">' => [ + 'scheme' => 'http://', + 'user' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => 80, + 'path' => '/something', + 'query' => '', + 'fragment' => '%22%3E%3Cscript%3Ealert%3C/script%3E', + + 'route' => '/something', + 'paths' => ['something'], + 'params' => null, + 'url' => '/something', + 'environment' => 'localhost', + 'basename' => 'something', + 'base' => 'http://localhost', + 'currentPage' => 1, + 'rootUrl' => 'http://localhost', + 'extension' => null, + 'addNonce' => 'http://localhost/something/nonce:{{nonce}}#%22%3E%3Cscript%3Ealert%3C/script%3E', + 'toOriginalString' => 'http://localhost/something#%22%3E%3Cscript%3Ealert%3C/script%3E' + ], + 'https://www.getgrav.org/something/"><' => [ + 'scheme' => 'https://', + 'user' => null, + 'password' => null, + 'host' => 'www.getgrav.org', + 'port' => 443, + 'path' => '/something/%22%3E%3Cscript%3Eeval%28atob%28%22aGlzdG9yeS5wdXNoU3RhdGUoJycsJycsJy8nKTskKCdoZWFkLGJvZHknKS5odG1sKCcnKS5sb2FkKCcvJyk7JC5wb3N0KCcvYWRtaW4nLGZ1bmN0aW9uKGRhdGEpeyQucG9zdCgkKGRhdGEpLmZpbmQoJ1tpZD1hZG1pbi11c2VyLWRldGFpbHNdIGEnKS5hdHRyKCdocmVmJykseydhZG1pbi1ub25jZSc6JChkYXRhKS5maW5kKCdbZGF0YS1jbGVhci1jYWNoZV0nKS5hdHRyKCdkYXRhLWNsZWFyLWNhY2hlJykuc3BsaXQoJzonKS5wb3AoKS50cmltKCksJ2RhdGFbcGFzc3dvcmRdJzonSW0zdjFsaDR4eDByJywndGFzayc6J3NhdmUnfSl9KQ==%22%29%29%3C/script%3E%3C', + 'query' => '', + 'fragment' => null, + + 'route' => '/something/%22%3E%3Cscript%3Eeval%28atob%28%22aGlzdG9yeS5wdXNoU3RhdGUoJycsJycsJy8nKTskKCdoZWFkLGJvZHknKS5odG1sKCcnKS5sb2FkKCcvJyk7JC5wb3N0KCcvYWRtaW4nLGZ1bmN0aW9uKGRhdGEpeyQucG9zdCgkKGRhdGEpLmZpbmQoJ1tpZD1hZG1pbi11c2VyLWRldGFpbHNdIGEnKS5hdHRyKCdocmVmJykseydhZG1pbi1ub25jZSc6JChkYXRhKS5maW5kKCdbZGF0YS1jbGVhci1jYWNoZV0nKS5hdHRyKCdkYXRhLWNsZWFyLWNhY2hlJykuc3BsaXQoJzonKS5wb3AoKS50cmltKCksJ2RhdGFbcGFzc3dvcmRdJzonSW0zdjFsaDR4eDByJywndGFzayc6J3NhdmUnfSl9KQ==%22%29%29%3C/script%3E%3C', + 'paths' => ['something', '%22%3E%3Cscript%3Eeval%28atob%28%22aGlzdG9yeS5wdXNoU3RhdGUoJycsJycsJy8nKTskKCdoZWFkLGJvZHknKS5odG1sKCcnKS5sb2FkKCcvJyk7JC5wb3N0KCcvYWRtaW4nLGZ1bmN0aW9uKGRhdGEpeyQucG9zdCgkKGRhdGEpLmZpbmQoJ1tpZD1hZG1pbi11c2VyLWRldGFpbHNdIGEnKS5hdHRyKCdocmVmJykseydhZG1pbi1ub25jZSc6JChkYXRhKS5maW5kKCdbZGF0YS1jbGVhci1jYWNoZV0nKS5hdHRyKCdkYXRhLWNsZWFyLWNhY2hlJykuc3BsaXQoJzonKS5wb3AoKS50cmltKCksJ2RhdGFbcGFzc3dvcmRdJzonSW0zdjFsaDR4eDByJywndGFzayc6J3NhdmUnfSl9KQ==%22%29%29%3C', 'script%3E%3C'], + 'params' => null, + 'url' => '/something/%22%3E%3Cscript%3Eeval%28atob%28%22aGlzdG9yeS5wdXNoU3RhdGUoJycsJycsJy8nKTskKCdoZWFkLGJvZHknKS5odG1sKCcnKS5sb2FkKCcvJyk7JC5wb3N0KCcvYWRtaW4nLGZ1bmN0aW9uKGRhdGEpeyQucG9zdCgkKGRhdGEpLmZpbmQoJ1tpZD1hZG1pbi11c2VyLWRldGFpbHNdIGEnKS5hdHRyKCdocmVmJykseydhZG1pbi1ub25jZSc6JChkYXRhKS5maW5kKCdbZGF0YS1jbGVhci1jYWNoZV0nKS5hdHRyKCdkYXRhLWNsZWFyLWNhY2hlJykuc3BsaXQoJzonKS5wb3AoKS50cmltKCksJ2RhdGFbcGFzc3dvcmRdJzonSW0zdjFsaDR4eDByJywndGFzayc6J3NhdmUnfSl9KQ==%22%29%29%3C/script%3E%3C', + 'environment' => 'www.getgrav.org', + 'basename' => 'script%3E%3C', + 'base' => 'https://www.getgrav.org', + 'currentPage' => 1, + 'rootUrl' => 'https://www.getgrav.org', + 'extension' => null, + 'addNonce' => 'https://www.getgrav.org/something/%22%3E%3Cscript%3Eeval%28atob%28%22aGlzdG9yeS5wdXNoU3RhdGUoJycsJycsJy8nKTskKCdoZWFkLGJvZHknKS5odG1sKCcnKS5sb2FkKCcvJyk7JC5wb3N0KCcvYWRtaW4nLGZ1bmN0aW9uKGRhdGEpeyQucG9zdCgkKGRhdGEpLmZpbmQoJ1tpZD1hZG1pbi11c2VyLWRldGFpbHNdIGEnKS5hdHRyKCdocmVmJykseydhZG1pbi1ub25jZSc6JChkYXRhKS5maW5kKCdbZGF0YS1jbGVhci1jYWNoZV0nKS5hdHRyKCdkYXRhLWNsZWFyLWNhY2hlJykuc3BsaXQoJzonKS5wb3AoKS50cmltKCksJ2RhdGFbcGFzc3dvcmRdJzonSW0zdjFsaDR4eDByJywndGFzayc6J3NhdmUnfSl9KQ==%22%29%29%3C/script%3E%3C/nonce:{{nonce}}', + 'toOriginalString' => 'https://www.getgrav.org/something/%22%3E%3Cscript%3Eeval%28atob%28%22aGlzdG9yeS5wdXNoU3RhdGUoJycsJycsJy8nKTskKCdoZWFkLGJvZHknKS5odG1sKCcnKS5sb2FkKCcvJyk7JC5wb3N0KCcvYWRtaW4nLGZ1bmN0aW9uKGRhdGEpeyQucG9zdCgkKGRhdGEpLmZpbmQoJ1tpZD1hZG1pbi11c2VyLWRldGFpbHNdIGEnKS5hdHRyKCdocmVmJykseydhZG1pbi1ub25jZSc6JChkYXRhKS5maW5kKCdbZGF0YS1jbGVhci1jYWNoZV0nKS5hdHRyKCdkYXRhLWNsZWFyLWNhY2hlJykuc3BsaXQoJzonKS5wb3AoKS50cmltKCksJ2RhdGFbcGFzc3dvcmRdJzonSW0zdjFsaDR4eDByJywndGFzayc6J3NhdmUnfSl9KQ==%22%29%29%3C/script%3E%3C' + ], + ]; + + protected function _before(): void + { + $grav = Fixtures::get('grav'); + $this->grav = $grav(); + $this->uri = $this->grav['uri']; + $this->config = $this->grav['config']; + } + + protected function _after(): void + { + } + + protected function runTestSet(array $tests, $method, $params = []): void + { + foreach ($tests as $url => $candidates) { + if (!array_key_exists($method, $candidates) && $method !== 'toOriginalString') { + continue; + } + if ($method === 'addNonce') { + $nonce = Utils::getNonce('test-action'); + $expected = str_replace('{{nonce}}', $nonce, $candidates[$method]); + + self::assertSame($expected, Uri::addNonce($url, 'test-action')); + + continue; + } + + $this->uri->initializeWithURL($url)->init(); + if ($method === 'toOriginalString' && !isset($candidates[$method])) { + $expected = $url; + } else { + $expected = $candidates[$method]; + } + + if ($params) { + $result = call_user_func_array([$this->uri, $method], $params); + } else { + $result = $this->uri->{$method}(); + } + + self::assertSame($expected, $result, "Test \$url->{$method}() for {$url}"); + // Deal with $url->query($key) + if ($method === 'query') { + parse_str($expected, $queryParams); + foreach ($queryParams as $key => $value) { + self::assertSame($value, $this->uri->{$method}($key), "Test \$url->{$method}('{$key}') for {$url}"); + } + self::assertNull($this->uri->{$method}('non-existing'), "Test \$url->{$method}('non-existing') for {$url}"); + } + } + } + + public function testValidatingHostname(): void + { + self::assertTrue($this->uri->validateHostname('localhost')); + self::assertTrue($this->uri->validateHostname('google.com')); + self::assertTrue($this->uri->validateHostname('google.it')); + self::assertTrue($this->uri->validateHostname('goog.le')); + self::assertTrue($this->uri->validateHostname('goog.wine')); + self::assertTrue($this->uri->validateHostname('goog.localhost')); + + self::assertFalse($this->uri->validateHostname('localhost:80')); + self::assertFalse($this->uri->validateHostname('http://localhost')); + self::assertFalse($this->uri->validateHostname('localhost!')); + } + + public function testToString(): void + { + $this->runTestSet($this->tests, 'toOriginalString'); + } + + public function testScheme(): void + { + $this->runTestSet($this->tests, 'scheme'); + } + + public function testUser(): void + { + $this->runTestSet($this->tests, 'user'); + } + + public function testPassword(): void + { + $this->runTestSet($this->tests, 'password'); + } + + public function testHost(): void + { + $this->runTestSet($this->tests, 'host'); + } + + public function testPort(): void + { + $this->runTestSet($this->tests, 'port'); + } + + public function testPath(): void + { + $this->runTestSet($this->tests, 'path'); + } + + public function testQuery(): void + { + $this->runTestSet($this->tests, 'query'); + } + + public function testFragment(): void + { + $this->runTestSet($this->tests, 'fragment'); + + $this->uri->fragment('something-new'); + self::assertSame('something-new', $this->uri->fragment()); + } + + public function testPaths(): void + { + $this->runTestSet($this->tests, 'paths'); + } + + public function testRoute(): void + { + $this->runTestSet($this->tests, 'route'); + } + + public function testParams(): void + { + $this->runTestSet($this->tests, 'params'); + + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper:xxx')->init(); + self::assertSame('/ueper:xxx', $this->uri->params('ueper')); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper:xxx/test:yyy')->init(); + self::assertSame('/ueper:xxx', $this->uri->params('ueper')); + self::assertSame('/test:yyy', $this->uri->params('test')); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper:xxx++/test:yyy')->init(); + self::assertSame('/ueper:xxx++/test:yyy', $this->uri->params()); + self::assertSame('/ueper:xxx++', $this->uri->params('ueper')); + self::assertSame('/test:yyy', $this->uri->params('test')); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper:xxx++/test:yyy#something')->init(); + self::assertSame('/ueper:xxx++/test:yyy', $this->uri->params()); + self::assertSame('/ueper:xxx++', $this->uri->params('ueper')); + self::assertSame('/test:yyy', $this->uri->params('test')); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper:xxx++/test:yyy?foo=bar')->init(); + self::assertSame('/ueper:xxx++/test:yyy', $this->uri->params()); + self::assertSame('/ueper:xxx++', $this->uri->params('ueper')); + self::assertSame('/test:yyy', $this->uri->params('test')); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x')->init(); + self::assertNull($this->uri->params()); + self::assertNull($this->uri->params('ueper')); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x&test2=y')->init(); + self::assertNull($this->uri->params()); + self::assertNull($this->uri->params('ueper')); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x&test2=y&test3=x&test4=y')->init(); + self::assertNull($this->uri->params()); + self::assertNull($this->uri->params('ueper')); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper?test=x&test2=y&test3=x&test4=y/test')->init(); + self::assertNull($this->uri->params()); + self::assertNull($this->uri->params('ueper')); + $this->uri->initializeWithURL('http://localhost:8080/a/b/c/d')->init(); + self::assertNull($this->uri->params()); + self::assertNull($this->uri->params('ueper')); + $this->uri->initializeWithURL('http://localhost:8080/a/b/c/d/e/f/a/b/c/d/e/f/a/b/c/d/e/f')->init(); + self::assertNull($this->uri->params()); + self::assertNull($this->uri->params('ueper')); + } + + public function testParam(): void + { + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper:xxx')->init(); + self::assertSame('xxx', $this->uri->param('ueper')); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper:xxx/test:yyy')->init(); + self::assertSame('xxx', $this->uri->param('ueper')); + self::assertSame('yyy', $this->uri->param('test')); + $this->uri->initializeWithURL('http://localhost:8080/grav/it/ueper:xxx++/test:yy%20y/foo:bar_baz-bank')->init(); + self::assertSame('xxx++', $this->uri->param('ueper')); + self::assertSame('yy y', $this->uri->param('test')); + self::assertSame('bar_baz-bank', $this->uri->param('foo')); + } + + public function testUrl(): void + { + $this->runTestSet($this->tests, 'url'); + } + + public function testExtension(): void + { + $this->runTestSet($this->tests, 'extension'); + + $this->uri->initializeWithURL('http://localhost/a-page')->init(); + self::assertSame('x', $this->uri->extension('x')); + } + + public function testEnvironment(): void + { + $this->runTestSet($this->tests, 'environment'); + } + + public function testBasename(): void + { + $this->runTestSet($this->tests, 'basename'); + } + + public function testBase(): void + { + $this->runTestSet($this->tests, 'base'); + } + + public function testRootUrl(): void + { + $this->runTestSet($this->tests, 'rootUrl', [true]); + + $this->uri->initializeWithUrlAndRootPath('https://localhost/grav/page-foo', '/grav')->init(); + self::assertSame('/grav', $this->uri->rootUrl()); + self::assertSame('https://localhost/grav', $this->uri->rootUrl(true)); + } + + public function testCurrentPage(): void + { + $this->runTestSet($this->tests, 'currentPage'); + + $this->uri->initializeWithURL('http://localhost:8080/a-page/page:2')->init(); + self::assertSame(2, $this->uri->currentPage()); + } + + public function testReferrer(): void + { + $this->uri->initializeWithURL('http://localhost/foo/page:test')->init(); + self::assertSame('/foo', $this->uri->referrer()); + $this->uri->initializeWithURL('http://localhost/foo/bar/page:test')->init(); + self::assertSame('/foo/bar', $this->uri->referrer()); + } + + public function testIp(): void + { + $this->uri->initializeWithURL('http://localhost/foo/page:test')->init(); + self::assertSame('UNKNOWN', Uri::ip()); + } + + public function testIsExternal(): void + { + $this->uri->initializeWithURL('http://localhost/')->init(); + self::assertFalse(Uri::isExternal('/test')); + self::assertFalse(Uri::isExternal('/foo/bar')); + self::assertTrue(Uri::isExternal('http://localhost/test')); + self::assertTrue(Uri::isExternal('http://google.it/test')); + } + + public function testBuildUrl(): void + { + $parsed_url = [ + 'scheme' => 'http', + 'host' => 'localhost', + 'port' => 8080, + ]; + + self::assertSame('http://localhost:8080', Uri::buildUrl($parsed_url)); + + $parsed_url = [ + 'scheme' => 'http', + 'host' => 'localhost', + 'port' => 8080, + 'user' => 'foo', + 'pass' => 'bar', + 'path' => '/test', + 'query' => 'x=2', + 'fragment' => 'xxx', + ]; + + self::assertSame('http://foo:bar@localhost:8080/test?x=2#xxx', Uri::buildUrl($parsed_url)); + + /** @var Uri $uri */ + $uri = Grav::instance()['uri']; + + $uri->initializeWithUrlAndRootPath('https://testing.dev/subdir/path1/path2/file.html', '/subdir')->init(); + self::assertSame('https://testing.dev/subdir/path1/path2/file.html', Uri::buildUrl($uri->toArray(true))); + + $uri->initializeWithUrlAndRootPath('https://testing.dev/subdir/path1/path2/file.foo', '/subdir')->init(); + self::assertSame('https://testing.dev/subdir/path1/path2/file.foo', Uri::buildUrl($uri->toArray(true))); + + $uri->initializeWithUrlAndRootPath('https://testing.dev/subdir/path1/path2/file.html', '/subdir/path1')->init(); + self::assertSame('https://testing.dev/subdir/path1/path2/file.html', Uri::buildUrl($uri->toArray(true))); + + $uri->initializeWithUrlAndRootPath('https://testing.dev/subdir/path1/path2/file.html/foo:blah/bang:boom', '/subdir')->init(); + self::assertSame('https://testing.dev/subdir/path1/path2/file.html/foo:blah/bang:boom', Uri::buildUrl($uri->toArray(true))); + + $uri->initializeWithUrlAndRootPath('https://testing.dev/subdir/path1/path2/file.html/foo:blah/bang:boom?fig=something', '/subdir')->init(); + self::assertSame('https://testing.dev/subdir/path1/path2/file.html/foo:blah/bang:boom?fig=something', Uri::buildUrl($uri->toArray(true))); + } + + public function testConvertUrl(): void + { + } + + public function testAddNonce(): void + { + $this->runTestSet($this->tests, 'addNonce'); + } + + public function testCustomBase(): void + { + $current_base = $this->config->get('system.custom_base_url'); + $this->config->set('system.custom_base_url', '/test'); + $this->uri->initializeWithURL('https://mydomain.example.com:8090/test/korteles/kodai%20something?test=true#some-fragment')->init(); + + $this->assertSame([ + "scheme" => "https", + "host" => "mydomain.example.com", + "port" => 8090, + "user" => null, + "pass" => null, + "path" => "/korteles/kodai%20something", + "params" => [], + "query" => "test=true", + "fragment" => "some-fragment", + ], $this->uri->toArray()); + + $this->config->set('system.custom_base_url', $current_base); + } +} diff --git a/tests/unit/Grav/Common/UtilsTest.php b/tests/unit/Grav/Common/UtilsTest.php new file mode 100644 index 0000000..9a29ad7 --- /dev/null +++ b/tests/unit/Grav/Common/UtilsTest.php @@ -0,0 +1,572 @@ +grav = $grav(); + $this->uri = $this->grav['uri']; + } + + protected function _after(): void + { + } + + public function testStartsWith(): void + { + self::assertTrue(Utils::startsWith('english', 'en')); + self::assertTrue(Utils::startsWith('English', 'En')); + self::assertTrue(Utils::startsWith('ENGLISH', 'EN')); + self::assertTrue(Utils::startsWith( + 'ENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISH', + 'EN' + )); + + self::assertFalse(Utils::startsWith('english', 'En')); + self::assertFalse(Utils::startsWith('English', 'EN')); + self::assertFalse(Utils::startsWith('ENGLISH', 'en')); + self::assertFalse(Utils::startsWith( + 'ENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISH', + 'e' + )); + + self::assertTrue(Utils::startsWith('english', 'En', false)); + self::assertTrue(Utils::startsWith('English', 'EN', false)); + self::assertTrue(Utils::startsWith('ENGLISH', 'en', false)); + self::assertTrue(Utils::startsWith( + 'ENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISH', + 'e', + false + )); + } + + public function testEndsWith(): void + { + self::assertTrue(Utils::endsWith('english', 'sh')); + self::assertTrue(Utils::endsWith('EngliSh', 'Sh')); + self::assertTrue(Utils::endsWith('ENGLISH', 'SH')); + self::assertTrue(Utils::endsWith( + 'ENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISH', + 'ENGLISH' + )); + + self::assertFalse(Utils::endsWith('english', 'de')); + self::assertFalse(Utils::endsWith('EngliSh', 'sh')); + self::assertFalse(Utils::endsWith('ENGLISH', 'Sh')); + self::assertFalse(Utils::endsWith( + 'ENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISH', + 'DEUSTCH' + )); + + self::assertTrue(Utils::endsWith('english', 'SH', false)); + self::assertTrue(Utils::endsWith('EngliSh', 'sH', false)); + self::assertTrue(Utils::endsWith('ENGLISH', 'sh', false)); + self::assertTrue(Utils::endsWith( + 'ENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISH', + 'english', + false + )); + } + + public function testContains(): void + { + self::assertTrue(Utils::contains('english', 'nglis')); + self::assertTrue(Utils::contains('EngliSh', 'gliSh')); + self::assertTrue(Utils::contains('ENGLISH', 'ENGLI')); + self::assertTrue(Utils::contains( + 'ENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISH', + 'ENGLISH' + )); + + self::assertFalse(Utils::contains('EngliSh', 'GLI')); + self::assertFalse(Utils::contains('EngliSh', 'English')); + self::assertFalse(Utils::contains('ENGLISH', 'SCH')); + self::assertFalse(Utils::contains( + 'ENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISH', + 'DEUSTCH' + )); + + self::assertTrue(Utils::contains('EngliSh', 'GLI', false)); + self::assertTrue(Utils::contains('EngliSh', 'ENGLISH', false)); + self::assertTrue(Utils::contains('ENGLISH', 'ish', false)); + self::assertTrue(Utils::contains( + 'ENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISHENGLISH', + 'english', + false + )); + } + + public function testSubstrToString(): void + { + self::assertEquals('en', Utils::substrToString('english', 'glish')); + self::assertEquals('english', Utils::substrToString('english', 'test')); + self::assertNotEquals('en', Utils::substrToString('english', 'lish')); + + self::assertEquals('en', Utils::substrToString('english', 'GLISH', false)); + self::assertEquals('english', Utils::substrToString('english', 'TEST', false)); + self::assertNotEquals('en', Utils::substrToString('english', 'LISH', false)); + } + + public function testMergeObjects(): void + { + $obj1 = new stdClass(); + $obj1->test1 = 'x'; + $obj2 = new stdClass(); + $obj2->test2 = 'y'; + + $objMerged = Utils::mergeObjects($obj1, $obj2); + + self::arrayHasKey('test1', (array) $objMerged); + self::arrayHasKey('test2', (array) $objMerged); + } + + public function testDateFormats(): void + { + $dateFormats = Utils::dateFormats(); + self::assertIsArray($dateFormats); + self::assertContainsOnly('string', $dateFormats); + + $default_format = $this->grav['config']->get('system.pages.dateformat.default'); + + if ($default_format !== null) { + self::assertArrayHasKey($default_format, $dateFormats); + } + } + + public function testTruncate(): void + { + self::assertEquals('engli' . '…', Utils::truncate('english', 5)); + self::assertEquals('english', Utils::truncate('english')); + self::assertEquals('This is a string to truncate', Utils::truncate('This is a string to truncate')); + self::assertEquals('Th' . '…', Utils::truncate('This is a string to truncate', 2)); + self::assertEquals('engli' . '...', Utils::truncate('english', 5, true, " ", "...")); + self::assertEquals('english', Utils::truncate('english')); + self::assertEquals('This is a string to truncate', Utils::truncate('This is a string to truncate')); + self::assertEquals('This' . '…', Utils::truncate('This is a string to truncate', 3, true)); + self::assertEquals('', 6, true)); + } + + public function testSafeTruncate(): void + { + self::assertEquals('This' . '…', Utils::safeTruncate('This is a string to truncate', 1)); + self::assertEquals('This' . '…', Utils::safeTruncate('This is a string to truncate', 4)); + self::assertEquals('This is' . '…', Utils::safeTruncate('This is a string to truncate', 5)); + } + + public function testTruncateHtml(): void + { + self::assertEquals('T...', Utils::truncateHtml('This is a string to truncate', 1)); + self::assertEquals('This is...', Utils::truncateHtml('This is a string to truncate', 7)); + self::assertEquals('

    T...

    ', Utils::truncateHtml('

    This is a string to truncate

    ', 1)); + self::assertEquals('

    This...

    ', Utils::truncateHtml('

    This is a string to truncate

    ', 4)); + self::assertEquals('

    This is a...

    ', Utils::truncateHtml('

    This is a string to truncate

    ', 10)); + self::assertEquals('

    This is a string to truncate

    ', Utils::truncateHtml('

    This is a string to truncate

    ', 100)); + self::assertEquals('', Utils::truncateHtml('', 6)); + self::assertEquals('
    1. item 1 so...
    ', Utils::truncateHtml('
    1. item 1 something
    2. item 2 bold
    ', 10)); + self::assertEquals("

    This is a string.

    \n

    It splits two lines.

    ", Utils::truncateHtml("

    This is a string.

    \n

    It splits two lines.

    ", 100)); + } + + public function testSafeTruncateHtml(): void + { + self::assertEquals('This...', Utils::safeTruncateHtml('This is a string to truncate', 1)); + self::assertEquals('This is a...', Utils::safeTruncateHtml('This is a string to truncate', 3)); + self::assertEquals('

    This...

    ', Utils::safeTruncateHtml('

    This is a string to truncate

    ', 1)); + self::assertEquals('

    This is...

    ', Utils::safeTruncateHtml('

    This is a string to truncate

    ', 2)); + self::assertEquals('

    This is a string to...

    ', Utils::safeTruncateHtml('

    This is a string to truncate

    ', 5)); + self::assertEquals('

    This is a string to truncate

    ', Utils::safeTruncateHtml('

    This is a string to truncate

    ', 20)); + self::assertEquals('', Utils::safeTruncateHtml('', 6)); + self::assertEquals('
    1. item 1 something
    2. item 2...
    ', Utils::safeTruncateHtml('
    1. item 1 something
    2. item 2 bold
    ', 5)); + } + + public function testGenerateRandomString(): void + { + self::assertNotEquals(Utils::generateRandomString(), Utils::generateRandomString()); + self::assertNotEquals(Utils::generateRandomString(20), Utils::generateRandomString(20)); + } + + public function download(): void + { + } + + public function testGetMimeByExtension(): void + { + self::assertEquals('application/octet-stream', Utils::getMimeByExtension('')); + self::assertEquals('text/html', Utils::getMimeByExtension('html')); + self::assertEquals('application/json', Utils::getMimeByExtension('json')); + self::assertEquals('application/atom+xml', Utils::getMimeByExtension('atom')); + self::assertEquals('application/rss+xml', Utils::getMimeByExtension('rss')); + self::assertEquals('image/jpeg', Utils::getMimeByExtension('jpg')); + self::assertEquals('image/png', Utils::getMimeByExtension('png')); + self::assertEquals('text/plain', Utils::getMimeByExtension('txt')); + self::assertEquals('application/msword', Utils::getMimeByExtension('doc')); + self::assertEquals('application/octet-stream', Utils::getMimeByExtension('foo')); + self::assertEquals('foo/bar', Utils::getMimeByExtension('foo', 'foo/bar')); + self::assertEquals('text/html', Utils::getMimeByExtension('foo', 'text/html')); + } + + public function testGetExtensionByMime(): void + { + self::assertEquals('html', Utils::getExtensionByMime('*/*')); + self::assertEquals('html', Utils::getExtensionByMime('text/*')); + self::assertEquals('html', Utils::getExtensionByMime('text/html')); + self::assertEquals('json', Utils::getExtensionByMime('application/json')); + self::assertEquals('atom', Utils::getExtensionByMime('application/atom+xml')); + self::assertEquals('rss', Utils::getExtensionByMime('application/rss+xml')); + self::assertEquals('jpg', Utils::getExtensionByMime('image/jpeg')); + self::assertEquals('png', Utils::getExtensionByMime('image/png')); + self::assertEquals('txt', Utils::getExtensionByMime('text/plain')); + self::assertEquals('doc', Utils::getExtensionByMime('application/msword')); + self::assertEquals('html', Utils::getExtensionByMime('foo/bar')); + self::assertEquals('baz', Utils::getExtensionByMime('foo/bar', 'baz')); + } + + public function testNormalizePath(): void + { + self::assertEquals('/test', Utils::normalizePath('/test')); + self::assertEquals('test', Utils::normalizePath('test')); + self::assertEquals('test', Utils::normalizePath('../test')); + self::assertEquals('/test', Utils::normalizePath('/../test')); + self::assertEquals('/test2', Utils::normalizePath('/test/../test2')); + self::assertEquals('/test3', Utils::normalizePath('/test/../test2/../test3')); + + self::assertEquals('//cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css', Utils::normalizePath('//cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css')); + self::assertEquals('//use.fontawesome.com/releases/v5.8.1/css/all.css', Utils::normalizePath('//use.fontawesome.com/releases/v5.8.1/css/all.css')); + self::assertEquals('//use.fontawesome.com/releases/v5.8.1/webfonts/fa-brands-400.eot', Utils::normalizePath('//use.fontawesome.com/releases/v5.8.1/css/../webfonts/fa-brands-400.eot')); + + self::assertEquals('http://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css', Utils::normalizePath('http://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css')); + self::assertEquals('http://use.fontawesome.com/releases/v5.8.1/css/all.css', Utils::normalizePath('http://use.fontawesome.com/releases/v5.8.1/css/all.css')); + self::assertEquals('http://use.fontawesome.com/releases/v5.8.1/webfonts/fa-brands-400.eot', Utils::normalizePath('http://use.fontawesome.com/releases/v5.8.1/css/../webfonts/fa-brands-400.eot')); + + self::assertEquals('https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css', Utils::normalizePath('https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css')); + self::assertEquals('https://use.fontawesome.com/releases/v5.8.1/css/all.css', Utils::normalizePath('https://use.fontawesome.com/releases/v5.8.1/css/all.css')); + self::assertEquals('https://use.fontawesome.com/releases/v5.8.1/webfonts/fa-brands-400.eot', Utils::normalizePath('https://use.fontawesome.com/releases/v5.8.1/css/../webfonts/fa-brands-400.eot')); + } + + public function testIsFunctionDisabled(): void + { + $disabledFunctions = explode(',', ini_get('disable_functions')); + + if ($disabledFunctions[0]) { + self::assertEquals(Utils::isFunctionDisabled($disabledFunctions[0]), true); + } + } + + public function testTimezones(): void + { + $timezones = Utils::timezones(); + + self::assertIsArray($timezones); + self::assertContainsOnly('string', $timezones); + } + + public function testArrayFilterRecursive(): void + { + $array = [ + 'test' => '', + 'test2' => 'test2' + ]; + + $array = Utils::arrayFilterRecursive($array, function ($k, $v) { + return !(is_null($v) || $v === ''); + }); + + self::assertContainsOnly('string', $array); + self::assertArrayNotHasKey('test', $array); + self::assertArrayHasKey('test2', $array); + self::assertEquals('test2', $array['test2']); + } + + public function testPathPrefixedByLangCode(): void + { + $languagesEnabled = $this->grav['config']->get('system.languages.supported', []); + $arrayOfLanguages = ['en', 'de', 'it', 'es', 'dk', 'el']; + $languagesNotEnabled = array_diff($arrayOfLanguages, $languagesEnabled); + $oneLanguageNotEnabled = reset($languagesNotEnabled); + + if (count($languagesEnabled)) { + $languageCodePathPrefix = Utils::pathPrefixedByLangCode('/' . $languagesEnabled[0] . '/test'); + $this->assertIsString($languageCodePathPrefix); + $this->assertTrue(in_array($languageCodePathPrefix, $languagesEnabled)); + } + + self::assertFalse(Utils::pathPrefixedByLangCode('/' . $oneLanguageNotEnabled . '/test')); + self::assertFalse(Utils::pathPrefixedByLangCode('/test')); + self::assertFalse(Utils::pathPrefixedByLangCode('/xx')); + self::assertFalse(Utils::pathPrefixedByLangCode('/xx/')); + self::assertFalse(Utils::pathPrefixedByLangCode('/')); + } + + public function testDate2timestamp(): void + { + $timestamp = strtotime('10 September 2000'); + self::assertSame($timestamp, Utils::date2timestamp('10 September 2000')); + self::assertSame($timestamp, Utils::date2timestamp('2000-09-10 00:00:00')); + } + + public function testResolve(): void + { + $array = [ + 'test' => [ + 'test2' => 'test2Value' + ] + ]; + + self::assertEquals('test2Value', Utils::resolve($array, 'test.test2')); + } + + public function testGetDotNotation(): void + { + $array = [ + 'test' => [ + 'test2' => 'test2Value', + 'test3' => [ + 'test4' => 'test4Value' + ] + ] + ]; + + self::assertEquals('test2Value', Utils::getDotNotation($array, 'test.test2')); + self::assertEquals('test4Value', Utils::getDotNotation($array, 'test.test3.test4')); + self::assertEquals('defaultValue', Utils::getDotNotation($array, 'test.non_existent', 'defaultValue')); + } + + public function testSetDotNotation(): void + { + $array = [ + 'test' => [ + 'test2' => 'test2Value', + 'test3' => [ + 'test4' => 'test4Value' + ] + ] + ]; + + $new = [ + 'test1' => 'test1Value' + ]; + + Utils::setDotNotation($array, 'test.test3.test4', $new); + self::assertEquals('test1Value', $array['test']['test3']['test4']['test1']); + } + + public function testIsPositive(): void + { + self::assertTrue(Utils::isPositive(true)); + self::assertTrue(Utils::isPositive(1)); + self::assertTrue(Utils::isPositive('1')); + self::assertTrue(Utils::isPositive('yes')); + self::assertTrue(Utils::isPositive('on')); + self::assertTrue(Utils::isPositive('true')); + self::assertFalse(Utils::isPositive(false)); + self::assertFalse(Utils::isPositive(0)); + self::assertFalse(Utils::isPositive('0')); + self::assertFalse(Utils::isPositive('no')); + self::assertFalse(Utils::isPositive('off')); + self::assertFalse(Utils::isPositive('false')); + self::assertFalse(Utils::isPositive('some')); + self::assertFalse(Utils::isPositive(2)); + } + + public function testGetNonce(): void + { + self::assertIsString(Utils::getNonce('test-action')); + self::assertIsString(Utils::getNonce('test-action', true)); + self::assertSame(Utils::getNonce('test-action'), Utils::getNonce('test-action')); + self::assertNotSame(Utils::getNonce('test-action'), Utils::getNonce('test-action2')); + } + + public function testVerifyNonce(): void + { + self::assertTrue(Utils::verifyNonce(Utils::getNonce('test-action'), 'test-action')); + } + + public function testGetPagePathFromToken(): void + { + self::assertEquals('', Utils::getPagePathFromToken('')); + self::assertEquals('/test/path', Utils::getPagePathFromToken('/test/path')); + } + + public function testUrl(): void + { + $this->uri->initializeWithUrl('http://testing.dev/path1/path2')->init(); + + // Fail hard + self::assertSame(false, Utils::url('', true)); + self::assertSame(false, Utils::url('')); + self::assertSame(false, Utils::url(new stdClass())); + self::assertSame(false, Utils::url(['foo','bar','baz'])); + self::assertSame(false, Utils::url('user://does/not/exist')); + + // Fail Gracefully + self::assertSame('/', Utils::url('/', false, true)); + self::assertSame('/', Utils::url('', false, true)); + self::assertSame('/', Utils::url(new stdClass(), false, true)); + self::assertSame('/', Utils::url(['foo','bar','baz'], false, true)); + self::assertSame('/user/does/not/exist', Utils::url('user://does/not/exist', false, true)); + + // Simple paths + self::assertSame('/', Utils::url('/')); + self::assertSame('/path1', Utils::url('/path1')); + self::assertSame('/path1/path2', Utils::url('/path1/path2')); + self::assertSame('/random/path1/path2', Utils::url('/random/path1/path2')); + self::assertSame('/foobar.jpg', Utils::url('/foobar.jpg')); + self::assertSame('/path1/foobar.jpg', Utils::url('/path1/foobar.jpg')); + self::assertSame('/path1/path2/foobar.jpg', Utils::url('/path1/path2/foobar.jpg')); + self::assertSame('/random/path1/path2/foobar.jpg', Utils::url('/random/path1/path2/foobar.jpg')); + + // Simple paths with domain + self::assertSame('http://testing.dev/', Utils::url('/', true)); + self::assertSame('http://testing.dev/path1', Utils::url('/path1', true)); + self::assertSame('http://testing.dev/path1/path2', Utils::url('/path1/path2', true)); + self::assertSame('http://testing.dev/random/path1/path2', Utils::url('/random/path1/path2', true)); + self::assertSame('http://testing.dev/foobar.jpg', Utils::url('/foobar.jpg', true)); + self::assertSame('http://testing.dev/path1/foobar.jpg', Utils::url('/path1/foobar.jpg', true)); + self::assertSame('http://testing.dev/path1/path2/foobar.jpg', Utils::url('/path1/path2/foobar.jpg', true)); + self::assertSame('http://testing.dev/random/path1/path2/foobar.jpg', Utils::url('/random/path1/path2/foobar.jpg', true)); + + // Relative paths from Grav root. + self::assertSame('/subdir', Utils::url('subdir')); + self::assertSame('/subdir/path1', Utils::url('subdir/path1')); + self::assertSame('/subdir/path1/path2', Utils::url('subdir/path1/path2')); + self::assertSame('/path1', Utils::url('path1')); + self::assertSame('/path1/path2', Utils::url('path1/path2')); + self::assertSame('/foobar.jpg', Utils::url('foobar.jpg')); + self::assertSame('http://testing.dev/foobar.jpg', Utils::url('foobar.jpg', true)); + + // Relative paths from Grav root with domain. + self::assertSame('http://testing.dev/foobar.jpg', Utils::url('foobar.jpg', true)); + self::assertSame('http://testing.dev/foobar.jpg', Utils::url('/foobar.jpg', true)); + self::assertSame('http://testing.dev/path1/foobar.jpg', Utils::url('/path1/foobar.jpg', true)); + + // All Non-existing streams should be treated as external URI / protocol. + self::assertSame('http://domain.com/path', Utils::url('http://domain.com/path')); + self::assertSame('ftp://domain.com/path', Utils::url('ftp://domain.com/path')); + self::assertSame('sftp://domain.com/path', Utils::url('sftp://domain.com/path')); + self::assertSame('ssh://domain.com', Utils::url('ssh://domain.com')); + self::assertSame('pop://domain.com', Utils::url('pop://domain.com')); + self::assertSame('foo://bar/baz', Utils::url('foo://bar/baz')); + self::assertSame('foo://bar/baz', Utils::url('foo://bar/baz', true)); + self::assertSame('mailto:joe@domain.com', Utils::url('mailto:joe@domain.com', true)); // FIXME <- + } + + public function testUrlWithRoot(): void + { + $this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/path1/path2', '/subdir')->init(); + + // Fail hard + self::assertSame(false, Utils::url('', true)); + self::assertSame(false, Utils::url('')); + self::assertSame(false, Utils::url(new stdClass())); + self::assertSame(false, Utils::url(['foo','bar','baz'])); + self::assertSame(false, Utils::url('user://does/not/exist')); + + // Fail Gracefully + self::assertSame('/subdir/', Utils::url('/', false, true)); + self::assertSame('/subdir/', Utils::url('', false, true)); + self::assertSame('/subdir/', Utils::url(new stdClass(), false, true)); + self::assertSame('/subdir/', Utils::url(['foo','bar','baz'], false, true)); + self::assertSame('/subdir/user/does/not/exist', Utils::url('user://does/not/exist', false, true)); + + // Simple paths + self::assertSame('/subdir/', Utils::url('/')); + self::assertSame('/subdir/path1', Utils::url('/path1')); + self::assertSame('/subdir/path1/path2', Utils::url('/path1/path2')); + self::assertSame('/subdir/random/path1/path2', Utils::url('/random/path1/path2')); + self::assertSame('/subdir/foobar.jpg', Utils::url('/foobar.jpg')); + self::assertSame('/subdir/path1/foobar.jpg', Utils::url('/path1/foobar.jpg')); + self::assertSame('/subdir/path1/path2/foobar.jpg', Utils::url('/path1/path2/foobar.jpg')); + self::assertSame('/subdir/random/path1/path2/foobar.jpg', Utils::url('/random/path1/path2/foobar.jpg')); + + // Simple paths with domain + self::assertSame('http://testing.dev/subdir/', Utils::url('/', true)); + self::assertSame('http://testing.dev/subdir/path1', Utils::url('/path1', true)); + self::assertSame('http://testing.dev/subdir/path1/path2', Utils::url('/path1/path2', true)); + self::assertSame('http://testing.dev/subdir/random/path1/path2', Utils::url('/random/path1/path2', true)); + self::assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('/foobar.jpg', true)); + self::assertSame('http://testing.dev/subdir/path1/foobar.jpg', Utils::url('/path1/foobar.jpg', true)); + self::assertSame('http://testing.dev/subdir/path1/path2/foobar.jpg', Utils::url('/path1/path2/foobar.jpg', true)); + self::assertSame('http://testing.dev/subdir/random/path1/path2/foobar.jpg', Utils::url('/random/path1/path2/foobar.jpg', true)); + + // Absolute Paths including the grav base. + self::assertSame('/subdir/', Utils::url('/subdir')); + self::assertSame('/subdir/', Utils::url('/subdir/')); + self::assertSame('/subdir/path1', Utils::url('/subdir/path1')); + self::assertSame('/subdir/path1/path2', Utils::url('/subdir/path1/path2')); + self::assertSame('/subdir/foobar.jpg', Utils::url('/subdir/foobar.jpg')); + self::assertSame('/subdir/path1/foobar.jpg', Utils::url('/subdir/path1/foobar.jpg')); + + // Absolute paths from Grav root with domain. + self::assertSame('http://testing.dev/subdir/', Utils::url('/subdir', true)); + self::assertSame('http://testing.dev/subdir/', Utils::url('/subdir/', true)); + self::assertSame('http://testing.dev/subdir/path1', Utils::url('/subdir/path1', true)); + self::assertSame('http://testing.dev/subdir/path1/path2', Utils::url('/subdir/path1/path2', true)); + self::assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('/subdir/foobar.jpg', true)); + self::assertSame('http://testing.dev/subdir/path1/foobar.jpg', Utils::url('/subdir/path1/foobar.jpg', true)); + + // Relative paths from Grav root. + self::assertSame('/subdir/sub', Utils::url('/sub')); + self::assertSame('/subdir/subdir', Utils::url('subdir')); + self::assertSame('/subdir/subdir2/sub', Utils::url('/subdir2/sub')); + self::assertSame('/subdir/subdir/path1', Utils::url('subdir/path1')); + self::assertSame('/subdir/subdir/path1/path2', Utils::url('subdir/path1/path2')); + self::assertSame('/subdir/path1', Utils::url('path1')); + self::assertSame('/subdir/path1/path2', Utils::url('path1/path2')); + self::assertSame('/subdir/foobar.jpg', Utils::url('foobar.jpg')); + self::assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('foobar.jpg', true)); + + // All Non-existing streams should be treated as external URI / protocol. + self::assertSame('http://domain.com/path', Utils::url('http://domain.com/path')); + self::assertSame('ftp://domain.com/path', Utils::url('ftp://domain.com/path')); + self::assertSame('sftp://domain.com/path', Utils::url('sftp://domain.com/path')); + self::assertSame('ssh://domain.com', Utils::url('ssh://domain.com')); + self::assertSame('pop://domain.com', Utils::url('pop://domain.com')); + self::assertSame('foo://bar/baz', Utils::url('foo://bar/baz')); + self::assertSame('foo://bar/baz', Utils::url('foo://bar/baz', true)); + // self::assertSame('mailto:joe@domain.com', Utils::url('mailto:joe@domain.com', true)); // FIXME <- + } + + public function testUrlWithStreams(): void + { + } + + public function testUrlwithExternals(): void + { + $this->uri->initializeWithUrl('http://testing.dev/path1/path2')->init(); + self::assertSame('http://foo.com', Utils::url('http://foo.com')); + self::assertSame('https://foo.com', Utils::url('https://foo.com')); + self::assertSame('//foo.com', Utils::url('//foo.com')); + self::assertSame('//foo.com?param=x', Utils::url('//foo.com?param=x')); + } + + public function testCheckFilename(): void + { + // configure extension for consistent results + /** @var \Grav\Common\Config\Config $config */ + $config = $this->grav['config']; + $config->set('security.uploads_dangerous_extensions', ['php', 'html', 'htm', 'exe', 'js']); + + self::assertFalse(Utils::checkFilename('foo.php')); + self::assertFalse(Utils::checkFilename('foo.PHP')); + self::assertFalse(Utils::checkFilename('bar.js')); + + self::assertTrue(Utils::checkFilename('foo.json')); + self::assertTrue(Utils::checkFilename('foo.xml')); + self::assertTrue(Utils::checkFilename('foo.yaml')); + self::assertTrue(Utils::checkFilename('foo.yml')); + } +} diff --git a/tests/unit/Grav/Console/Gpm/InstallCommandTest.php b/tests/unit/Grav/Console/Gpm/InstallCommandTest.php new file mode 100644 index 0000000..94aef1a --- /dev/null +++ b/tests/unit/Grav/Console/Gpm/InstallCommandTest.php @@ -0,0 +1,28 @@ +grav = Fixtures::get('grav'); + $this->installCommand = new InstallCommand(); + } + + protected function _after(): void + { + } +} diff --git a/tests/unit/Grav/Framework/File/Formatter/CsvFormatterTest.php b/tests/unit/Grav/Framework/File/Formatter/CsvFormatterTest.php new file mode 100644 index 0000000..7bff4e2 --- /dev/null +++ b/tests/unit/Grav/Framework/File/Formatter/CsvFormatterTest.php @@ -0,0 +1,48 @@ + 1, 'col2' => 2, 'col3' => 3], + ['col1' => 'aaa', 'col2' => 'bbb', 'col3' => 'ccc'], + ]; + + $encoded = (new CsvFormatter())->encode($data); + + $lines = array_filter(explode(PHP_EOL, $encoded)); + + self::assertCount(3, $lines); + self::assertEquals('col1,col2,col3', $lines[0]); + } + + /** + * TBD - If indexes are all numeric, what's the purpose + * of displaying header + */ + public function testEncodeWithIndexColumns(): void + { + $data = [ + [0 => 1, 1 => 2, 2 => 3], + ]; + + $encoded = (new CsvFormatter())->encode($data); + + $lines = array_filter(explode(PHP_EOL, $encoded)); + + self::assertCount(2, $lines); + self::assertEquals('0,1,2', $lines[0]); + } + + public function testEncodeEmptyData(): void + { + $encoded = (new CsvFormatter())->encode([]); + self::assertEquals('', $encoded); + } +} diff --git a/tests/unit/Grav/Framework/Filesystem/FilesystemTest.php b/tests/unit/Grav/Framework/Filesystem/FilesystemTest.php new file mode 100644 index 0000000..2aea40c --- /dev/null +++ b/tests/unit/Grav/Framework/Filesystem/FilesystemTest.php @@ -0,0 +1,338 @@ + [ + 'parent' => '', + 'normalize' => '', + 'dirname' => '', + 'pathinfo' => [ + 'basename' => '', + 'filename' => '', + ] + ], + '.' => [ + 'parent' => '', + 'normalize' => '', + 'dirname' => '.', + 'pathinfo' => [ + 'dirname' => '.', + 'basename' => '.', + 'extension' => '', + 'filename' => '', + ] + ], + './' => [ + 'parent' => '', + 'normalize' => '', + 'dirname' => '.', + 'pathinfo' => [ + 'dirname' => '.', + 'basename' => '.', + 'extension' => '', + 'filename' => '', + ] + ], + '././.' => [ + 'parent' => '', + 'normalize' => '', + 'dirname' => './.', + 'pathinfo' => [ + 'dirname' => './.', + 'basename' => '.', + 'extension' => '', + 'filename' => '', + ] + ], + '.file' => [ + 'parent' => '.', + 'normalize' => '.file', + 'dirname' => '.', + 'pathinfo' => [ + 'dirname' => '.', + 'basename' => '.file', + 'extension' => 'file', + 'filename' => '', + ] + ], + '/' => [ + 'parent' => '', + 'normalize' => '/', + 'dirname' => '/', + 'pathinfo' => [ + 'dirname' => '/', + 'basename' => '', + 'filename' => '', + ] + ], + '/absolute' => [ + 'parent' => '/', + 'normalize' => '/absolute', + 'dirname' => '/', + 'pathinfo' => [ + 'dirname' => '/', + 'basename' => 'absolute', + 'filename' => 'absolute', + ] + ], + '/absolute/' => [ + 'parent' => '/', + 'normalize' => '/absolute', + 'dirname' => '/', + 'pathinfo' => [ + 'dirname' => '/', + 'basename' => 'absolute', + 'filename' => 'absolute', + ] + ], + '/very/long/absolute/path' => [ + 'parent' => '/very/long/absolute', + 'normalize' => '/very/long/absolute/path', + 'dirname' => '/very/long/absolute', + 'pathinfo' => [ + 'dirname' => '/very/long/absolute', + 'basename' => 'path', + 'filename' => 'path', + ] + ], + '/very/long/absolute/../path' => [ + 'parent' => '/very/long', + 'normalize' => '/very/long/path', + 'dirname' => '/very/long/absolute/..', + 'pathinfo' => [ + 'dirname' => '/very/long/absolute/..', + 'basename' => 'path', + 'filename' => 'path', + ] + ], + 'relative' => [ + 'parent' => '.', + 'normalize' => 'relative', + 'dirname' => '.', + 'pathinfo' => [ + 'dirname' => '.', + 'basename' => 'relative', + 'filename' => 'relative', + ] + ], + 'very/long/relative/path' => [ + 'parent' => 'very/long/relative', + 'normalize' => 'very/long/relative/path', + 'dirname' => 'very/long/relative', + 'pathinfo' => [ + 'dirname' => 'very/long/relative', + 'basename' => 'path', + 'filename' => 'path', + ] + ], + 'path/to/file.jpg' => [ + 'parent' => 'path/to', + 'normalize' => 'path/to/file.jpg', + 'dirname' => 'path/to', + 'pathinfo' => [ + 'dirname' => 'path/to', + 'basename' => 'file.jpg', + 'extension' => 'jpg', + 'filename' => 'file', + ] + ], + 'user://' => [ + 'parent' => '', + 'normalize' => 'user://', + 'dirname' => 'user://', + 'pathinfo' => [ + 'dirname' => 'user://', + 'basename' => '', + 'filename' => '', + 'scheme' => 'user', + ] + ], + 'user://.' => [ + 'parent' => '', + 'normalize' => 'user://', + 'dirname' => 'user://', + 'pathinfo' => [ + 'dirname' => 'user://', + 'basename' => '', + 'filename' => '', + 'scheme' => 'user', + ] + ], + 'user://././.' => [ + 'parent' => '', + 'normalize' => 'user://', + 'dirname' => 'user://', + 'pathinfo' => [ + 'dirname' => 'user://', + 'basename' => '', + 'filename' => '', + 'scheme' => 'user', + ] + ], + 'user://./././file' => [ + 'parent' => 'user://', + 'normalize' => 'user://file', + 'dirname' => 'user://', + 'pathinfo' => [ + 'dirname' => 'user://', + 'basename' => 'file', + 'filename' => 'file', + 'scheme' => 'user', + ] + ], + 'user://./././folder/file' => [ + 'parent' => 'user://folder', + 'normalize' => 'user://folder/file', + 'dirname' => 'user://folder', + 'pathinfo' => [ + 'dirname' => 'user://folder', + 'basename' => 'file', + 'filename' => 'file', + 'scheme' => 'user', + ] + ], + 'user://.file' => [ + 'parent' => 'user://', + 'normalize' => 'user://.file', + 'dirname' => 'user://', + 'pathinfo' => [ + 'dirname' => 'user://', + 'basename' => '.file', + 'extension' => 'file', + 'filename' => '', + 'scheme' => 'user', + ] + ], + 'user:///' => [ + 'parent' => '', + 'normalize' => 'user:///', + 'dirname' => 'user:///', + 'pathinfo' => [ + 'dirname' => 'user:///', + 'basename' => '', + 'filename' => '', + 'scheme' => 'user', + ] + ], + 'user:///absolute' => [ + 'parent' => 'user:///', + 'normalize' => 'user:///absolute', + 'dirname' => 'user:///', + 'pathinfo' => [ + 'dirname' => 'user:///', + 'basename' => 'absolute', + 'filename' => 'absolute', + 'scheme' => 'user', + ] + ], + 'user:///very/long/absolute/path' => [ + 'parent' => 'user:///very/long/absolute', + 'normalize' => 'user:///very/long/absolute/path', + 'dirname' => 'user:///very/long/absolute', + 'pathinfo' => [ + 'dirname' => 'user:///very/long/absolute', + 'basename' => 'path', + 'filename' => 'path', + 'scheme' => 'user', + ] + ], + 'user://relative' => [ + 'parent' => 'user://', + 'normalize' => 'user://relative', + 'dirname' => 'user://', + 'pathinfo' => [ + 'dirname' => 'user://', + 'basename' => 'relative', + 'filename' => 'relative', + 'scheme' => 'user', + ] + ], + 'user://very/long/relative/path' => [ + 'parent' => 'user://very/long/relative', + 'normalize' => 'user://very/long/relative/path', + 'dirname' => 'user://very/long/relative', + 'pathinfo' => [ + 'dirname' => 'user://very/long/relative', + 'basename' => 'path', + 'filename' => 'path', + 'scheme' => 'user', + ] + ], + 'user://path/to/file.jpg' => [ + 'parent' => 'user://path/to', + 'normalize' => 'user://path/to/file.jpg', + 'dirname' => 'user://path/to', + 'pathinfo' => [ + 'dirname' => 'user://path/to', + 'basename' => 'file.jpg', + 'extension' => 'jpg', + 'filename' => 'file', + 'scheme' => 'user', + ] + ], + ]; + + protected function _before(): void + { + $this->class = Filesystem::getInstance(); + } + + protected function _after(): void + { + unset($this->class); + } + + /** + * @param array $tests + * @param string $method + */ + protected function runTestSet(array $tests, $method): void + { + $class = $this->class; + foreach ($tests as $path => $candidates) { + if (!array_key_exists($method, $candidates)) { + continue; + } + + $expected = $candidates[$method]; + + $result = $class->{$method}($path); + + self::assertSame($expected, $result, "Test {$method}('{$path}')"); + + if (function_exists($method) && !strpos($path, '://')) { + $cmp_result = $method($path); + + self::assertSame($cmp_result, $result, "Compare to original {$method}('{$path}')"); + } + } + } + + public function testParent(): void + { + $this->runTestSet($this->tests, 'parent'); + } + + public function testNormalize(): void + { + $this->runTestSet($this->tests, 'normalize'); + } + + public function testDirname(): void + { + $this->runTestSet($this->tests, 'dirname'); + } + + public function testPathinfo(): void + { + $this->runTestSet($this->tests, 'pathinfo'); + } +} diff --git a/tests/unit/_bootstrap.php b/tests/unit/_bootstrap.php new file mode 100644 index 0000000..5a76515 --- /dev/null +++ b/tests/unit/_bootstrap.php @@ -0,0 +1,3 @@ +JSK`+P@DvD)`UaUM>d+1XILX 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 Quvj51a0KlA11|sk53B0lhX4Qo literal 0 HcmV?d00001 diff --git a/user/pages/01.home/default.md b/user/pages/01.home/default.md new file mode 100644 index 0000000..5b3ba76 --- /dev/null +++ b/user/pages/01.home/default.md @@ -0,0 +1,15 @@ +--- +title: Home +body_classes: 'title-center title-h1h2' +media_order: 'robot_detruit.png,robot_guerrier.png,robot_neuf.png,oeil_colere.png,oeil.gif,bulle_texte.png' +--- + + + + + + + + + + diff --git a/user/pages/01.home/oeil.gif b/user/pages/01.home/oeil.gif new file mode 100644 index 0000000000000000000000000000000000000000..a79b8d5d16484712ba362ebe2ca4354d72a36957 GIT binary patch literal 58678 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 literal 0 HcmV?d00001 diff --git a/user/pages/01.home/robot_detruit.png b/user/pages/01.home/robot_detruit.png new file mode 100644 index 0000000000000000000000000000000000000000..0e3002d01beb41d1b5356596623e5ae097f383f6 GIT binary patch literal 70610 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

    GWedt*mu-+ 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 close tag in bae template [#76](https://github.com/getgrav/grav-theme-quark/pull/76) + +# v1.2.4 +## 11/12/2018 + +1. [](#improved) + * Updated [Spectre.css](https://picturepan2.github.io/spectre/) to latest `0.5.5` version + * Added link support to modular `features` [#39](https://github.com/getgrav/grav-theme-quark/pull/39/) + * Remove desktop menu when in mobile mode [#59](https://github.com/getgrav/grav-theme-quark/pull/59/) + * Support modular `text` full-width if no image [#70](https://github.com/getgrav/grav-theme-quark/issues/70) + * Shim for IE support of BrickLayer.js [#64](https://github.com/getgrav/grav-theme-quark/issues/64) +1. [](#bugfix) + * Fixed `continue_link:` showing up as toggled [#65](https://github.com/getgrav/grav-theme-quark/issues/65) + * Fixed issue with modular pages not hidden in on-page menu with `visible: false` [#71](https://github.com/getgrav/grav-theme-quark/issues/71) + + +# v1.2.3 +## 11/05/2018 + +1. [](#improved) + * Moved footer into standalone twig to allow for easier extensibility [#63](https://github.com/getgrav/grav-theme-quark/pull/63) +1. [](#bugfix) + * Fix variable name for prouction mode [#61](https://github.com/getgrav/grav-theme-quark/pull/61) + * Fix layout size in features blueprint [#67](https://github.com/getgrav/grav-theme-quark/pull/67) + * Fix active page logic in `nav` so there's no empty class attributes [#68](https://github.com/getgrav/grav-theme-quark/pull/68) + * Fix for features blueprint because `class` didn't work [#69](https://github.com/getgrav/grav-theme-quark/pull/69) + +# v1.2.2 +## 10/24/2018 + +1. [](#improved) + * Changed nav macro to format supported by Twig 2.0 + * Updated `partials/form-messages.html.twig` to be more inline with latest Forms plugin +1. [](#bugfix) + * Make the theme to work with Twig auto-escaping turned on + * Moved language strings under `THEME_QUARK` + +# v1.2.1 +## 08/23/2018 + +1. [](#improved) + * Added additional "mobile custom logo" support +1. [](#bugfix) + * Addressed some CSS issues by forcing logo height + +# v1.2.0 +## 08/23/2018 + +1. [](#new) + * Added new "custom logo" support [#3](https://github.com/getgrav/grav-theme-quark/issues/3) + * Added option JSON feed syndication support in sidebar [#47](https://github.com/getgrav/grav-theme-quark/pull/47) + * Added basic form field `array` styling + +# v1.1.0 +## 07/25/2018 + +1. [](#new) + * Responsive font sizing [#28](https://github.com/getgrav/grav-theme-quark/issues/28) +1. [](#improved) + * Updated [Spectre.css](https://picturepan2.github.io/spectre/) to latest `0.5.3` version + * Make blog settings toggleable [#38](https://github.com/getgrav/grav-theme-quark/pull/38) +1. [](#bugfix) + * Proper fix for sticky footer in IE10 and IE11 [#21](https://github.com/getgrav/grav-theme-quark/issues/21) + * Fix for lists wrapping weirdly due to `outside` attribute + * Updated checkbox + radio to take into account `client_side_validation` form option + * Fixes for fallback values [#37](https://github.com/getgrav/grav-theme-quark/pull/37) + * Fix inheritance for images folder [#30](https://github.com/getgrav/grav-theme-quark/pull/30) + * Added blueprint option for `continue_link` [#45](https://github.com/getgrav/grav-theme-quark/issues/45) + * Added blueprint option for Feature `class` [#14](https://github.com/getgrav/grav-theme-quark/issues/14) + * Fixed `Duplicate ID` issues with modular sections. Might break CSS on first load, need to refresh to pick up new CSS [#24](https://github.com/getgrav/grav-theme-quark/issues/24) + * Fixed Text feature alignment issue [#4](https://github.com/getgrav/grav-theme-quark/issues/4) + * Overlapping menu and mobile button [#7](https://github.com/getgrav/grav-theme-quark/issues/7) + +# v1.0.3 +## 05/11/2018 + +1. [](#new) + * Added new primary button mixin +1. [](#improved) + * Updated [Spectre.css](https://picturepan2.github.io/spectre/) to latest `0.5.1` version + * Improved default login styling + * Removed core Spectre.css override to make upgrading Spectre easier + * Added screenshot to README.md + * Override focus to prevent overzealous blue blurs +1. [](#bugfix) + * Fix for `highlight` plugin not changing background of code blocks + * Removed extraneous `dump()` in Twig output + +# v1.0.2 +## 02/19/2018 + +1. [](#new) + * Added toggle options to enable Spectre.css _experimentals_ and _icons_ CSS files + * Switched to a fork of LineAwesome icons compatible with FontAwesome 4.7.0 +1. [](#improved) + * Font tweaks +1. [](#bugfix) + * Pagination fixes + +# v1.0.1 +## 01/22/2018 + +1. [](#new) + * Added blueprints for admin editing +1. [](#improved) + * Use default lang from `site.yaml` +1. [](#bugfix) + * Fixed Current path to address issues with extending Quark + * Fixed parallax to start in same position as standard + * Fixed modular image size + +# v1.0.0 +## 12/28/2017 + +1. [](#new) + * ChangeLog started... diff --git a/user/themes/test/LICENSE b/user/themes/test/LICENSE new file mode 100644 index 0000000..b5e7990 --- /dev/null +++ b/user/themes/test/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 Trilby Media + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/user/themes/test/README.md b/user/themes/test/README.md new file mode 100644 index 0000000..655d2f0 --- /dev/null +++ b/user/themes/test/README.md @@ -0,0 +1,153 @@ +# Quark Theme + +![](assets/quark-screenshots.jpg) + +**Quark** is the new default theme for [Grav CMS](http://github.com/getgrav/grav). This theme is built with the [Spectre.css](https://picturepan2.github.io/spectre/) framework and provides a powerful base for developing your own themes. Quark uses functionality that is only available in Grav 1.4+, as such you cannot run Quark on earlier versions of Grav. + +## Features + +* Lightweight and minimal for optimal performance +* Spectre CSS Framework +* Fully responsive with full-page mobile navigation +* SCSS based CSS source files for easy customization +* Built-in support for on-page navigation +* Multiple page template types +* Fontawesome icon support + +### Supported Page Templates + +* Default view template `default.md` +* Error view template `error.md` +* Blog view template `blog.md` +* Blog item view template `item.md` +* Modular view templates: `modular.md` + * Features Modular view template `features.md` + * Hero Modular view template `hero.md` + * Text Modular view template `text.md` + * Note: Gallery Modular view template `gallery.md` only works in concert with premium plugin [Lightbox Gallery](https://getgrav.org/premium/lightbox-gallery/docs) + +# Installation + +Installing the Quark theme can be done in one of two ways. Our GPM (Grav Package Manager) installation method enables you to quickly and easily install the theme with a simple terminal command, while the manual method enables you to do so via a zip file. + +The theme by itself is useful, but you may have an easier time getting up and running by installing a skeleton. The Quark theme can be found in both the [One-page](https://github.com/getgrav/grav-skeleton-onepage-site) and [Blog Site](https://github.com/getgrav/grav-skeleton-blog-site) which are self-contained repositories for a complete sites which include: sample content, configuration, theme, and plugins. + +## GPM Installation (Preferred) + +The simplest way to install this theme is via the [Grav Package Manager (GPM)](http://learn.getgrav.org/advanced/grav-gpm) through your system's Terminal (also called the command line). From the root of your Grav install type: + + bin/gpm install quark + +This will install the Quark theme into your `/user/themes` directory within Grav. Its files can be found under `/your/site/grav/user/themes/quark`. + +## Manual Installation + +To install this theme, just download the zip version of this repository and unzip it under `/your/site/grav/user/themes`. Then, rename the folder to `quark`. You can find these files either on [GitHub](https://github.com/getgrav/grav-theme-quark) or via [GetGrav.org](http://getgrav.org/downloads/themes). + +You should now have all the theme files under + + /your/site/grav/user/themes/quark + +## Default Options + +Quark comes with a few default options that can be set site-wide. These options are: + +```yaml +enabled: true # Enable the theme +production-mode: true # In production mode, only minified CSS is used. When disabled, nested CSS with sourcemaps are enabled +grid-size: grid-lg # The max-width of the theme, options include: `grid-xl`, `grid-lg`, and `grid-md` +header-fixed: true # Cause the header to be fixed at the top of the browser +header-animated: true # Allows the fixed header to resize to a smaller header when scrolled +header-dark: false # Inverts the text/logo to work better on dark backgrounds +header-transparent: false # Allows the fixed header to be transparent over the page +sticky-footer: true # Causes the footer to be sticky at the bottom of the page +blog-page: '/blog' # The route to the blog listing page, useful for a blog style layout with sidebar +custom_logo: # A custom logo rather than the default (see below) +custom_logo_mobile: # A custom logo to use for mobile navigation +``` + +To make modifications, you can copy the `user/themes/quark/quark.yaml` file to `user/config/themes/` folder and modify, or you can use the admin plugin. + +> NOTE: Do not modify the `user/themes/quark/quark.yaml` file directly or your changes will be lost with any updates + +## Custom Logos + +To add a custom logo, you should put the log into the `user/themes/quark/images/logo` folder. Standard image formats are support (`.png`,`.jpg`, `.gif`, `.svg`, etc.). Then reference the logo via the YAML like so: + +```yaml +custom_logo: + - name: 'my-logo.png' +custom_logo_mobile: + - name: 'my-mobile-logo.png' +``` + +Alternatively, you can you use the drag-n-drop "Custom Logo" field in the Quark theme options. + +## Page Overrides + +Quark has the ability to allow pages to override some of the default options by letting the user set `body_classes` for any page. The theme will merge the combination of the defaults with any `body_classes` set. For example: + +```yaml +body_classes: "header-dark header-transparent" +``` + +On a particular page will ensure that page has those options enabled (assuming they are false by default). + +## Hero Options + +The hero template allows some options to be set in the page frontmatter. This is used by the modular `hero` as well as the blog and item templates to provide a more dynamic header. + +```yaml +hero_classes: text-light title-h1h2 parallax overlay-dark-gradient hero-large +hero_image: road.jpg +hero_align: center +``` + +The `hero_classes` option allows a variety of hero classes to be set dynamically these include: + +* `text-light` | `text-dark` - Controls if the text should be light or dark depending on the content +* `title-h1h2` - Enforced a close matched h1/h2 title pairing +* `parallax` - Enables a CSS-powered parallax effect +* `overlay-dark-gradient` - Displays a transparent gradient which further darkens the underlying image +* `overlay-light-gradient` - Displays a transparent gradient which further lightens the underlying image +* `overlay-dark` - Displays a solid transparent overlay which further darkens the underlying image +* `overlay-light` - Displays a solid transparent overlay which further darkens the underlying image +* `hero-fullscreen` | `hero-large` | `hero-medium` | `hero-small` | `hero-tiny` - Size of the hero block + +The `hero_image` should point to an image file in the current page folder. + +## Features Modular Options + +The features modular template provides the ability to set a class on the features, as well as an array of feature items. For example: + +```yaml +class: offset-box +features: + - header: Crazy Fast + text: "Performance is not just an afterthought, we baked it in from the start!" + icon: fighter-jet + - header: Easy to build + text: "Simple text files means Grav is trivial to install, and easy to maintain" + icon: database + - header: Awesome Technology + text: "Grav employs best-in-class technologies such as Twig, Markdown & Yaml" + icon: cubes + - header: Super Flexible + text: "From the ground up, with many plugin hooks, Grav is extremely extensible" + icon: object-ungroup + - header: Abundant Plugins + text: "A vibrant developer community means over 200 themes available to download" + icon: puzzle-piece + - header: Free / Open Source + text: "Grav is an open source project, so you can spend your money on other stuff" + icon: money +``` + +## Text Modular Options + +The text box provides a single option to control if any image found in the page folder should be left or right aligned: + +```yaml +image_align: right +``` + diff --git a/user/themes/test/assets/quark-screenshots.jpg b/user/themes/test/assets/quark-screenshots.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b4b0c91632643c7cfbc899125bca62946b9b0788 GIT binary patch literal 198055 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

    jORfvnj)4CCX*2DlS=)w3f(#Ea^d#f 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%0ANWvO>eds0N>RQ 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

    u(;#Hp~Gfy;+UH|P_>l$Dcz>X4S*Kb9i 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%0A1LBGgaf?0jYQ| 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 literal 0 HcmV?d00001 diff --git a/user/themes/test/blueprints.yaml b/user/themes/test/blueprints.yaml new file mode 100644 index 0000000..1e2a92b --- /dev/null +++ b/user/themes/test/blueprints.yaml @@ -0,0 +1,176 @@ +name: Quark +slug: quark +type: theme +version: 2.1.2 +description: New Grav Default Theme +icon: microchip +author: + name: Team Grav + email: devs@getgrav.org + url: https://getgrav.org +homepage: https://github.com/getgrav/grav-theme-quark +demo: https://demo.getgrav.org/onepage-skeleton +keywords: quark, spectre, theme, core, modern, fast, responsive, html5, css3 +bugs: https://github.com/getgrav/grav-theme-quark/issues +license: MIT + +dependencies: + - { name: grav, version: '>=1.6.0' } + +form: + validation: loose + + fields: + production-mode: + type: toggle + label: THEME_QUARK.ADMIN.PRODUCTION_MODE + help: THEME_QUARK.ADMIN.PRODUCTION_MODE_HELP + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + grid-size: + type: select + label: THEME_QUARK.ADMIN.GRID_SIZE + help: THEME_QUARK.ADMIN.GRID_SIZE_HELP + size: small + options: + '': THEME_QUARK.ADMIN.GRID_SIZE_NONE + grid-xl: THEME_QUARK.ADMIN.GRID_SIZE_EXTRA_LARGE + grid-lg: THEME_QUARK.ADMIN.GRID_SIZE_LARGE + grid-md: THEME_QUARK.ADMIN.GRID_SIZE_MEDIUM + + header_section: + type: section + title: THEME_QUARK.ADMIN.HEADER_DEFAULTS + underline: true + + custom_logo: + type: file + label: THEME_QUARK.ADMIN.CUSTOM_LOGO + size: large + destination: 'theme://images/logo' + multiple: false + markdown: true + description: THEME_QUARK.ADMIN.CUSTOM_LOGO_DESCRIPTION + accept: + - image/* + + custom_logo_mobile: + type: file + label: THEME_QUARK.ADMIN.CUSTOM_LOGO_MOBILE + size: large + destination: 'theme://images/logo' + multiple: false + accept: + - image/* + + header-fixed: + type: toggle + label: THEME_QUARK.ADMIN.HEADER_FIXED + help: THEME_QUARK.ADMIN.HEADER_FIXED_HELP + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + header-animated: + type: toggle + label: THEME_QUARK.ADMIN.HEADER_ANIMATED + help: THEME_QUARK.ADMIN.HEADER_ANIMATED_HELP + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + header-dark: + type: toggle + label: THEME_QUARK.ADMIN.HEADER_DARK + help: THEME_QUARK.ADMIN.HEADER_DARK_HELP + highlight: 0 + default: 0 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + header-transparent: + type: toggle + label: THEME_QUARK.ADMIN.HEADER_TRANSPARENT + help: THEME_QUARK.ADMIN.HEADER_TRANSPARENT_HELP + highlight: 0 + default: 0 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + footer_section: + type: section + title: THEME_QUARK.ADMIN.FOOTER_DEFAULTS + underline: true + + sticky-footer: + type: toggle + label: THEME_QUARK.ADMIN.STICKY_FOOTER + help: THEME_QUARK.ADMIN.STICKY_FOOTER_HELP + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + blog_section: + type: section + title: THEME_QUARK.ADMIN.BLOG_DEFAULTS + underline: true + + blog-page: + type: text + label: THEME_QUARK.ADMIN.BLOG_PAGE + help: THEME_QUARK.ADMIN.BLOG_PAGE_HELP + size: medium + default: '/blog' + + spectre_section: + type: section + title: THEME_QUARK.ADMIN.SPECTRE_OPTIONS + underline: true + + spectre.exp: + type: toggle + label: THEME_QUARK.ADMIN.SPECTRE_EXP + help: THEME_QUARK.ADMIN.SPECTRE_EXP_HELP + highlight: 0 + default: 0 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + spectre.icons: + type: toggle + label: THEME_QUARK.ADMIN.SPECTRE_ICONS + help: THEME_QUARK.ADMIN.SPECTRE_ICONS_HELP + highlight: 0 + default: 0 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool diff --git a/user/themes/test/blueprints/blog.yaml b/user/themes/test/blueprints/blog.yaml new file mode 100644 index 0000000..8600143 --- /dev/null +++ b/user/themes/test/blueprints/blog.yaml @@ -0,0 +1,94 @@ +extends@: default +child_type: item + +rules: + slug: + pattern: "[a-z][a-z0-9_-]+" + min: 2 + max: 80 + +form: + fields: + tabs: + type: tabs + active: 1 + + fields: + advanced: + fields: + overrides: + fields: + header.child_type: + default: item + blog: + type: tab + title: Blog Config + + fields: + + content_title: + type: spacer + title: Content Definition + + header.content.items: + type: textarea + yaml: true + label: Items + default: '@self.children' + validate: + type: yaml + + header.content.limit: + type: text + label: Max Item Count + default: 5 + validate: + required: true + type: int + min: 1 + + header.content.order.by: + type: select + label: Order By + default: date + options: + folder: Folder + title: Title + date: Date + default: Default + + header.content.order.dir: + type: select + label: Order + default: desc + options: + asc: Ascending + desc: Descending + + header.content.pagination: + type: toggle + label: Pagination + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + header.content.url_taxonomy_filters: + type: toggle + label: URL Taxonomy Filters + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + import@: + type: partials/blog-bits + context: blueprints://pages + + diff --git a/user/themes/test/blueprints/default.yaml b/user/themes/test/blueprints/default.yaml new file mode 100644 index 0000000..3219221 --- /dev/null +++ b/user/themes/test/blueprints/default.yaml @@ -0,0 +1,15 @@ +extends@: default + +form: + fields: + tabs: + fields: + advanced: + fields: + columns: + fields: + column1: + fields: + header.body_classes: + markdown: true + description: 'Available classes in Quark Theme (space separated):
    `header-fixed`, `header-animated`, `header-dark`, `header-transparent`, `sticky-footer`' \ No newline at end of file diff --git a/user/themes/test/blueprints/item.yaml b/user/themes/test/blueprints/item.yaml new file mode 100644 index 0000000..60cc3e1 --- /dev/null +++ b/user/themes/test/blueprints/item.yaml @@ -0,0 +1,113 @@ +extends@: default + +form: + fields: + tabs: + + fields: + blog: + type: tab + title: Blog Item + + fields: + + header_options: + type: section + title: Header Options + underline: true + + header.continue_link: + type: toggle + toggleable: true + label: DF Style Link + help: Daring Fireball style title link + highlight: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + header.header_image: + type: toggle + toggleable: true + label: Display Header Image + help: Enabled displaying of a header image + highlight: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + + header.header_image_file: + type: text + toggleable: true + label: Image File + help: image filename that exists in the page folder. If not provided, will use the first image found. + placeholder: "For example: myimage.jpg" + + header.header_image_width: + type: text + toggleable: true + label: Image Width + size: small + help: Header width in px + placeholder: Default is 900 + validate: + type: int + min: 0 + max: 5000 + + header.header_image_height: + type: text + toggleable: true + label: Image Height + size: small + help: Header height in px + placeholder: Default is 300 + validate: + type: int + min: 0 + max: 5000 + + summary: + type: section + title: Summary + underline: true + + header.summary.enabled: + type: toggle + toggleable: true + label: Summary + highlight: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + + header.summary.format: + type: select + toggleable: true + label: Format + classes: fancy + options: + 'short': 'Use the first occurence of delimiter or size' + 'long': 'Summary delimiter will be ignored' + + header.summary.size: + type: text + toggleable: true + label: Size + classes: large + placeholder: 300 + validate: + type: int + min: 1 + + header.summary.delimiter: + type: text + toggleable: true + label: Summary delimiter + classes: large + placeholder: === + + import@: + type: partials/blog-bits diff --git a/user/themes/test/blueprints/modular.yaml b/user/themes/test/blueprints/modular.yaml new file mode 100644 index 0000000..9d7fa2f --- /dev/null +++ b/user/themes/test/blueprints/modular.yaml @@ -0,0 +1,47 @@ +title: PLUGIN_ADMIN.MODULE +extends@: default + +form: + fields: + tabs: + type: tabs + active: 1 + + fields: + content: + fields: + + modular_title: + type: spacer + title: PLUGIN_ADMIN.MODULE_SETUP + + header.onpage_menu: + type: toggle + style: vertical + label: THEME_QUARK.ADMIN.MODULAR.COMMON.SHOW_ONPAGE_MENU + help: THEME_QUARK.ADMIN.MODULAR.COMMON.SHOW_ONPAGE_MENU_HELP + default: 0 + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + + header.content.items: + type: text + label: PLUGIN_ADMIN.ITEMS + default: '@self.modular' + size: medium + + header.content.order.by: + type: text + label: PLUGIN_ADMIN.ORDER_BY + placeholder: date + help: + size: small + + header.content.order.dir: + type: text + label: PLUGIN_ADMIN.ORDER + help: '"desc" or "asc" are valid values' + placeholder: desc + size: small \ No newline at end of file diff --git a/user/themes/test/blueprints/modular/features.yaml b/user/themes/test/blueprints/modular/features.yaml new file mode 100644 index 0000000..187696f --- /dev/null +++ b/user/themes/test/blueprints/modular/features.yaml @@ -0,0 +1,38 @@ +title: Features +'@extends': default + +form: + fields: + tabs: + fields: + features: + type: tab + title: Features + fields: + header.class: + type: select + label: Layout + default: small + size: medium + options: + small: Small = 4 / 3 / 2 columns + standard: Standard = 3 / 2 / 1 columns + + header.features: + name: features + type: list + label: Features + + fields: + .icon: + type: iconpicker + label: Icon + .header: + type: text + label: Header + .text: + type: text + label: Text + .url: + type: text + label: Link diff --git a/user/themes/test/blueprints/modular/hero.yaml b/user/themes/test/blueprints/modular/hero.yaml new file mode 100644 index 0000000..5e8abf5 --- /dev/null +++ b/user/themes/test/blueprints/modular/hero.yaml @@ -0,0 +1,23 @@ +title: Hero +'@extends': default + +form: + fields: + tabs: + fields: + buttons: + type: tab + title: Hero + fields: + header.hero_classes: + type: text + label: Hero Classes + markdown: true + description: 'There are several Hero class options that can be listed here (space separated):
    `text-light`, `text-dark`, `title-h1h2`, `parallax`, `overlay-dark-gradient`, `overlay-light-gradient`, `overlay-dark`, `overlay-light`, `hero-fullscreen`, `hero-large`, `hero-medium`, `hero-small`, `hero-tiny`
    Please consult the [Quark documentation](https://github.com/getgrav/grav-theme-quark#hero-options) for more details.' + header.hero_image: + type: filepicker + label: Hero Image + preview_images: true + description: 'If not specified, this defaults to the first image found in the page''s folder' + + diff --git a/user/themes/test/blueprints/modular/text.yaml b/user/themes/test/blueprints/modular/text.yaml new file mode 100644 index 0000000..023c272 --- /dev/null +++ b/user/themes/test/blueprints/modular/text.yaml @@ -0,0 +1,19 @@ +title: Text +'@extends': default + +form: + fields: + tabs: + fields: + content: + fields: + header.media_order: + label: Page Media (first one will be displayed next to your content) + header.image_align: + type: select + label: Image position + classes: fancy + default: left + options: + 'left': 'Left' + 'right': 'Right' diff --git a/user/themes/test/blueprints/partials/blog-bits.yaml b/user/themes/test/blueprints/partials/blog-bits.yaml new file mode 100644 index 0000000..6ab4148 --- /dev/null +++ b/user/themes/test/blueprints/partials/blog-bits.yaml @@ -0,0 +1,64 @@ +form: + fields: + + hero_title: + type: spacer + title: Hero Section + + header.hero_classes: + type: text + label: Hero Classes + markdown: true + description: 'There are several Hero class options that can be listed here (space separated):
    `text-light`, `text-dark`, `title-h1h2`, `parallax`, `overlay-dark-gradient`, `overlay-light-gradient`, `overlay-dark`, `overlay-light`, `hero-fullscreen`, `hero-large`, `hero-medium`, `hero-small`, `hero-tiny`
    Please consult the [Quark documentation](https://github.com/getgrav/grav-theme-quark#hero-options) for more details.' + + header.hero_image: + type: filepicker + label: Hero Image + preview_images: true + description: 'If not specified, this defaults to the first image found in the page''s folder' + + toggles_title: + type: spacer + title: Configuration + + header.blog_url: + type: text + toggleable: true + label: Blog Route + help: The route to the main blog page that contains the "Show ..." configuration + default: '/blog' + placeholder: '/blog' + size: medium + + header.show_sidebar: + type: toggle + toggleable: true + label: Show Sidebar + highlight: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + header.show_breadcrumbs: + type: toggle + toggleable: true + label: Show Breadcrumbs + highlight: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + header.show_pagination: + type: toggle + toggleable: true + label: Show Pagination + highlight: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool \ No newline at end of file diff --git a/user/themes/test/css-compiled/spectre-exp.css b/user/themes/test/css-compiled/spectre-exp.css new file mode 100755 index 0000000..6eadf7a --- /dev/null +++ b/user/themes/test/css-compiled/spectre-exp.css @@ -0,0 +1,369 @@ +/*! Spectre.css Experimentals v0.5.8 | MIT License | github.com/picturepan2/spectre */ +.form-autocomplete { position: relative; } + +.form-autocomplete .form-autocomplete-input { -ms-flex-line-pack: start; align-content: flex-start; display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; height: auto; min-height: 1.6rem; padding: 0.1rem; } + +.form-autocomplete .form-autocomplete-input.is-focused { box-shadow: 0 0 0 0.1rem rgba(48, 133, 238, 0.2); border-color: #3085EE; } + +.form-autocomplete .form-autocomplete-input .form-input { border-color: transparent; box-shadow: none; display: inline-block; -ms-flex: 1 0 auto; flex: 1 0 auto; height: 1.2rem; line-height: 0.8rem; margin: 0.1rem; width: auto; } + +.form-autocomplete .menu { left: 0; position: absolute; top: 100%; width: 100%; } + +.form-autocomplete.autocomplete-oneline .form-autocomplete-input { -ms-flex-wrap: nowrap; flex-wrap: nowrap; overflow-x: auto; } + +.form-autocomplete.autocomplete-oneline .chip { -ms-flex: 1 0 auto; flex: 1 0 auto; } + +.calendar { border: 0.05rem solid #e7e9ed; border-radius: 0.1rem; display: block; min-width: 280px; } + +.calendar .calendar-nav { -ms-flex-align: center; align-items: center; background: #f8f9fa; border-top-left-radius: 0.1rem; border-top-right-radius: 0.1rem; display: -ms-flexbox; display: flex; font-size: 0.9rem; padding: 0.4rem; } + +.calendar .calendar-header, .calendar .calendar-body { display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; -ms-flex-pack: center; justify-content: center; padding: 0.4rem 0; } + +.calendar .calendar-header .calendar-date, .calendar .calendar-body .calendar-date { -ms-flex: 0 0 14.28%; flex: 0 0 14.28%; max-width: 14.28%; } + +.calendar .calendar-header { background: #f8f9fa; border-bottom: 0.05rem solid #e7e9ed; color: #acb3c2; font-size: 0.7rem; text-align: center; } + +.calendar .calendar-body { color: #667189; } + +.calendar .calendar-date { border: 0; padding: 0.2rem; } + +.calendar .calendar-date .date-item { -webkit-appearance: none; -moz-appearance: none; appearance: none; background: transparent; border: 0.05rem solid transparent; border-radius: 50%; color: #667189; cursor: pointer; font-size: 0.7rem; height: 1.4rem; line-height: 1rem; outline: none; padding: 0.1rem; position: relative; text-align: center; text-decoration: none; transition: background .2s, border .2s, box-shadow .2s, color .2s; vertical-align: middle; white-space: nowrap; width: 1.4rem; } + +.calendar .calendar-date .date-item.date-today { border-color: #d3e5fb; color: #3085EE; } + +.calendar .calendar-date .date-item:focus { box-shadow: 0 0 0 0.1rem rgba(48, 133, 238, 0.2); } + +.calendar .calendar-date .date-item:focus, .calendar .calendar-date .date-item:hover { background: #eff5fe; border-color: #d3e5fb; color: #3085EE; text-decoration: none; } + +.calendar .calendar-date .date-item:active, .calendar .calendar-date .date-item.active { background: #227ded; border-color: #1370e3; color: #fff; } + +.calendar .calendar-date .date-item.badge::after { position: absolute; top: 3px; right: 3px; transform: translate(50%, -50%); } + +.calendar .calendar-date .date-item:disabled, .calendar .calendar-date .date-item.disabled, .calendar .calendar-date .calendar-event:disabled, .calendar .calendar-date .calendar-event.disabled { cursor: default; opacity: .25; pointer-events: none; } + +.calendar .calendar-date.prev-month .date-item, .calendar .calendar-date.prev-month .calendar-event, .calendar .calendar-date.next-month .date-item, .calendar .calendar-date.next-month .calendar-event { opacity: .25; } + +.calendar .calendar-range { position: relative; } + +.calendar .calendar-range::before { background: #e1edfd; content: ""; height: 1.4rem; left: 0; position: absolute; right: 0; top: 50%; transform: translateY(-50%); } + +.calendar .calendar-range.range-start::before { left: 50%; } + +.calendar .calendar-range.range-end::before { right: 50%; } + +.calendar .calendar-range.range-start .date-item, .calendar .calendar-range.range-end .date-item { background: #227ded; border-color: #1370e3; color: #fff; } + +.calendar .calendar-range .date-item { color: #3085EE; } + +.calendar.calendar-lg .calendar-body { padding: 0; } + +.calendar.calendar-lg .calendar-body .calendar-date { border-bottom: 0.05rem solid #e7e9ed; border-right: 0.05rem solid #e7e9ed; display: -ms-flexbox; display: flex; -ms-flex-direction: column; flex-direction: column; height: 5.5rem; padding: 0; } + +.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n) { border-right: 0; } + +.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7) { border-bottom: 0; } + +.calendar.calendar-lg .date-item { -ms-flex-item-align: end; align-self: flex-end; height: 1.4rem; margin-right: 0.2rem; margin-top: 0.2rem; } + +.calendar.calendar-lg .calendar-range::before { top: 19px; } + +.calendar.calendar-lg .calendar-range.range-start::before { left: auto; width: 19px; } + +.calendar.calendar-lg .calendar-range.range-end::before { right: 19px; } + +.calendar.calendar-lg .calendar-events { -ms-flex-positive: 1; flex-grow: 1; line-height: 1; overflow-y: auto; padding: 0.2rem; } + +.calendar.calendar-lg .calendar-event { border-radius: 0.1rem; font-size: 0.7rem; display: block; margin: 0.1rem auto; overflow: hidden; padding: 3px 4px; text-overflow: ellipsis; white-space: nowrap; } + +.carousel .carousel-locator:nth-of-type(1):checked ~ .carousel-container .carousel-item:nth-of-type(1), .carousel .carousel-locator:nth-of-type(2):checked ~ .carousel-container .carousel-item:nth-of-type(2), .carousel .carousel-locator:nth-of-type(3):checked ~ .carousel-container .carousel-item:nth-of-type(3), .carousel .carousel-locator:nth-of-type(4):checked ~ .carousel-container .carousel-item:nth-of-type(4), .carousel .carousel-locator:nth-of-type(5):checked ~ .carousel-container .carousel-item:nth-of-type(5), .carousel .carousel-locator:nth-of-type(6):checked ~ .carousel-container .carousel-item:nth-of-type(6), .carousel .carousel-locator:nth-of-type(7):checked ~ .carousel-container .carousel-item:nth-of-type(7), .carousel .carousel-locator:nth-of-type(8):checked ~ .carousel-container .carousel-item:nth-of-type(8) { animation: carousel-slidein .75s ease-in-out 1; opacity: 1; z-index: 100; } + +.carousel .carousel-locator:nth-of-type(1):checked ~ .carousel-nav .nav-item:nth-of-type(1), .carousel .carousel-locator:nth-of-type(2):checked ~ .carousel-nav .nav-item:nth-of-type(2), .carousel .carousel-locator:nth-of-type(3):checked ~ .carousel-nav .nav-item:nth-of-type(3), .carousel .carousel-locator:nth-of-type(4):checked ~ .carousel-nav .nav-item:nth-of-type(4), .carousel .carousel-locator:nth-of-type(5):checked ~ .carousel-nav .nav-item:nth-of-type(5), .carousel .carousel-locator:nth-of-type(6):checked ~ .carousel-nav .nav-item:nth-of-type(6), .carousel .carousel-locator:nth-of-type(7):checked ~ .carousel-nav .nav-item:nth-of-type(7), .carousel .carousel-locator:nth-of-type(8):checked ~ .carousel-nav .nav-item:nth-of-type(8) { color: #e7e9ed; } + +.carousel { background: #f8f9fa; display: block; overflow: hidden; position: relative; width: 100%; -webkit-overflow-scrolling: touch; z-index: 1; } + +.carousel .carousel-container { height: 100%; left: 0; position: relative; } + +.carousel .carousel-container::before { content: ""; display: block; padding-bottom: 56.25%; } + +.carousel .carousel-container .carousel-item { animation: carousel-slideout 1s ease-in-out 1; height: 100%; left: 0; margin: 0; opacity: 0; position: absolute; top: 0; width: 100%; } + +.carousel .carousel-container .carousel-item:hover .item-prev, .carousel .carousel-container .carousel-item:hover .item-next { opacity: 1; } + +.carousel .carousel-container .item-prev, .carousel .carousel-container .item-next { background: rgba(231, 233, 237, 0.25); border-color: rgba(231, 233, 237, 0.5); color: #e7e9ed; opacity: 0; position: absolute; top: 50%; transition: all .4s; transform: translateY(-50%); z-index: 100; } + +.carousel .carousel-container .item-prev { left: 1rem; } + +.carousel .carousel-container .item-next { right: 1rem; } + +.carousel .carousel-nav { bottom: 0.4rem; display: -ms-flexbox; display: flex; -ms-flex-pack: center; justify-content: center; left: 50%; position: absolute; transform: translateX(-50%); width: 10rem; z-index: 100; } + +.carousel .carousel-nav .nav-item { color: rgba(231, 233, 237, 0.5); display: block; -ms-flex: 1 0 auto; flex: 1 0 auto; height: 1.6rem; margin: 0.2rem; max-width: 2.5rem; position: relative; } + +.carousel .carousel-nav .nav-item::before { background: currentColor; content: ""; display: block; height: 0.1rem; position: absolute; top: .5rem; width: 100%; } + +@keyframes carousel-slidein { 0% { transform: translateX(100%); } + 100% { transform: translateX(0); } } + +@keyframes carousel-slideout { 0% { opacity: 1; + transform: translateX(0); } + 100% { opacity: 1; + transform: translateX(-50%); } } + +.comparison-slider { height: 50vh; overflow: hidden; position: relative; width: 100%; -webkit-overflow-scrolling: touch; } + +.comparison-slider .comparison-before, .comparison-slider .comparison-after { height: 100%; left: 0; margin: 0; overflow: hidden; position: absolute; top: 0; } + +.comparison-slider .comparison-before img, .comparison-slider .comparison-after img { height: 100%; object-fit: cover; object-position: left center; position: absolute; width: 100%; } + +.comparison-slider .comparison-before { width: 100%; z-index: 1; } + +.comparison-slider .comparison-before .comparison-label { right: 0.8rem; } + +.comparison-slider .comparison-after { max-width: 100%; min-width: 0; z-index: 2; } + +.comparison-slider .comparison-after::before { background: transparent; content: ""; cursor: default; height: 100%; left: 0; position: absolute; right: 0.8rem; top: 0; z-index: 1; } + +.comparison-slider .comparison-after::after { background: currentColor; border-radius: 50%; box-shadow: 0 -5px, 0 5px; color: #fff; content: ""; height: 3px; position: absolute; right: 0.4rem; top: 50%; transform: translate(50%, -50%); width: 3px; } + +.comparison-slider .comparison-after .comparison-label { left: 0.8rem; } + +.comparison-slider .comparison-resizer { animation: first-run 1.5s 1 ease-in-out; cursor: ew-resize; height: 0.8rem; left: 0; max-width: 100%; min-width: 0.8rem; opacity: 0; outline: none; position: relative; resize: horizontal; top: 50%; transform: translateY(-50%) scaleY(30); width: 0; } + +.comparison-slider .comparison-label { background: rgba(69, 77, 93, 0.5); bottom: 0.8rem; color: #fff; padding: 0.2rem 0.4rem; position: absolute; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } + +@keyframes first-run { 0% { width: 0; } + 25% { width: 2.4rem; } + 50% { width: 0.8rem; } + 75% { width: 1.2rem; } + 100% { width: 0; } } + +.filter .filter-tag#tag-0:checked ~ .filter-nav .chip[for="tag-0"], .filter .filter-tag#tag-1:checked ~ .filter-nav .chip[for="tag-1"], .filter .filter-tag#tag-2:checked ~ .filter-nav .chip[for="tag-2"], .filter .filter-tag#tag-3:checked ~ .filter-nav .chip[for="tag-3"], .filter .filter-tag#tag-4:checked ~ .filter-nav .chip[for="tag-4"], .filter .filter-tag#tag-5:checked ~ .filter-nav .chip[for="tag-5"], .filter .filter-tag#tag-6:checked ~ .filter-nav .chip[for="tag-6"], .filter .filter-tag#tag-7:checked ~ .filter-nav .chip[for="tag-7"], .filter .filter-tag#tag-8:checked ~ .filter-nav .chip[for="tag-8"] { background: #3085EE; color: #fff; } + +.filter .filter-tag#tag-1:checked ~ .filter-body .filter-item:not([data-tag~="tag-1"]), .filter .filter-tag#tag-2:checked ~ .filter-body .filter-item:not([data-tag~="tag-2"]), .filter .filter-tag#tag-3:checked ~ .filter-body .filter-item:not([data-tag~="tag-3"]), .filter .filter-tag#tag-4:checked ~ .filter-body .filter-item:not([data-tag~="tag-4"]), .filter .filter-tag#tag-5:checked ~ .filter-body .filter-item:not([data-tag~="tag-5"]), .filter .filter-tag#tag-6:checked ~ .filter-body .filter-item:not([data-tag~="tag-6"]), .filter .filter-tag#tag-7:checked ~ .filter-body .filter-item:not([data-tag~="tag-7"]), .filter .filter-tag#tag-8:checked ~ .filter-body .filter-item:not([data-tag~="tag-8"]) { display: none; } + +.filter .filter-nav { margin: 0.4rem 0; } + +.filter .filter-body { display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; } + +.meter { -webkit-appearance: none; -moz-appearance: none; appearance: none; background: #f8f9fa; border: 0; border-radius: 0.1rem; display: block; width: 100%; height: 0.8rem; } + +.meter::-webkit-meter-inner-element { display: block; } + +.meter::-webkit-meter-bar, .meter::-webkit-meter-optimum-value, .meter::-webkit-meter-suboptimum-value, .meter::-webkit-meter-even-less-good-value { border-radius: 0.1rem; } + +.meter::-webkit-meter-bar { background: #f8f9fa; } + +.meter::-webkit-meter-optimum-value { background: #32b643; } + +.meter::-webkit-meter-suboptimum-value { background: #ffb700; } + +.meter::-webkit-meter-even-less-good-value { background: #e85600; } + +.meter::-moz-meter-bar, .meter:-moz-meter-optimum, .meter:-moz-meter-sub-optimum, .meter:-moz-meter-sub-sub-optimum { border-radius: 0.1rem; } + +.meter:-moz-meter-optimum::-moz-meter-bar { background: #32b643; } + +.meter:-moz-meter-sub-optimum::-moz-meter-bar { background: #ffb700; } + +.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar { background: #e85600; } + +.off-canvas { display: -ms-flexbox; display: flex; -ms-flex-flow: nowrap; flex-flow: nowrap; height: 100%; position: relative; width: 100%; } + +.off-canvas .off-canvas-toggle { display: block; position: absolute; top: 0.4rem; transition: none; z-index: 1; left: 0.4rem; } + +.off-canvas .off-canvas-sidebar { background: #f8f9fa; bottom: 0; min-width: 10rem; overflow-y: auto; position: fixed; top: 0; transition: transform .25s; z-index: 200; left: 0; transform: translateX(-100%); } + +.off-canvas .off-canvas-content { -ms-flex: 1 1 auto; flex: 1 1 auto; height: 100%; padding: 0.4rem 0.4rem 0.4rem 4rem; } + +.off-canvas .off-canvas-overlay { background: rgba(69, 77, 93, 0.1); border-color: transparent; border-radius: 0; bottom: 0; display: none; height: 100%; left: 0; position: fixed; right: 0; top: 0; width: 100%; } + +.off-canvas .off-canvas-sidebar:target, .off-canvas .off-canvas-sidebar.active { transform: translateX(0); } + +.off-canvas .off-canvas-sidebar:target ~ .off-canvas-overlay, .off-canvas .off-canvas-sidebar.active ~ .off-canvas-overlay { display: block; z-index: 100; } + +@media (min-width: 960px) { .off-canvas.off-canvas-sidebar-show .off-canvas-toggle { display: none; } + .off-canvas.off-canvas-sidebar-show .off-canvas-sidebar { -ms-flex: 0 0 auto; flex: 0 0 auto; position: relative; transform: none; } + .off-canvas.off-canvas-sidebar-show .off-canvas-overlay { display: none !important; } } + +.parallax { display: block; height: auto; position: relative; width: auto; } + +.parallax .parallax-content { box-shadow: 0 1rem 2.1rem rgba(69, 77, 93, 0.3); height: auto; transform: perspective(1000px); transform-style: preserve-3d; transition: all .4s ease; width: 100%; } + +.parallax .parallax-content::before { content: ""; display: block; height: 100%; left: 0; position: absolute; top: 0; width: 100%; } + +.parallax .parallax-front { -ms-flex-align: center; align-items: center; color: #fff; display: -ms-flexbox; display: flex; height: 100%; -ms-flex-pack: center; justify-content: center; left: 0; position: absolute; text-align: center; text-shadow: 0 0 20px rgba(69, 77, 93, 0.75); top: 0; transform: translateZ(50px) scale(0.95); transition: transform .4s; width: 100%; z-index: 1; } + +.parallax .parallax-top-left { height: 50%; outline: none; position: absolute; width: 50%; z-index: 100; left: 0; top: 0; } + +.parallax .parallax-top-left:focus ~ .parallax-content, .parallax .parallax-top-left:hover ~ .parallax-content { transform: perspective(1000px) rotateX(3deg) rotateY(-3deg); } + +.parallax .parallax-top-left:focus ~ .parallax-content::before, .parallax .parallax-top-left:hover ~ .parallax-content::before { background: linear-gradient(135deg, rgba(255, 255, 255, 0.35) 0%, transparent 50%); } + +.parallax .parallax-top-left:focus ~ .parallax-content .parallax-front, .parallax .parallax-top-left:hover ~ .parallax-content .parallax-front { transform: translate3d(4.5px, 4.5px, 50px) scale(0.95); } + +.parallax .parallax-top-right { height: 50%; outline: none; position: absolute; width: 50%; z-index: 100; right: 0; top: 0; } + +.parallax .parallax-top-right:focus ~ .parallax-content, .parallax .parallax-top-right:hover ~ .parallax-content { transform: perspective(1000px) rotateX(3deg) rotateY(3deg); } + +.parallax .parallax-top-right:focus ~ .parallax-content::before, .parallax .parallax-top-right:hover ~ .parallax-content::before { background: linear-gradient(-135deg, rgba(255, 255, 255, 0.35) 0%, transparent 50%); } + +.parallax .parallax-top-right:focus ~ .parallax-content .parallax-front, .parallax .parallax-top-right:hover ~ .parallax-content .parallax-front { transform: translate3d(-4.5px, 4.5px, 50px) scale(0.95); } + +.parallax .parallax-bottom-left { height: 50%; outline: none; position: absolute; width: 50%; z-index: 100; bottom: 0; left: 0; } + +.parallax .parallax-bottom-left:focus ~ .parallax-content, .parallax .parallax-bottom-left:hover ~ .parallax-content { transform: perspective(1000px) rotateX(-3deg) rotateY(-3deg); } + +.parallax .parallax-bottom-left:focus ~ .parallax-content::before, .parallax .parallax-bottom-left:hover ~ .parallax-content::before { background: linear-gradient(45deg, rgba(255, 255, 255, 0.35) 0%, transparent 50%); } + +.parallax .parallax-bottom-left:focus ~ .parallax-content .parallax-front, .parallax .parallax-bottom-left:hover ~ .parallax-content .parallax-front { transform: translate3d(4.5px, -4.5px, 50px) scale(0.95); } + +.parallax .parallax-bottom-right { height: 50%; outline: none; position: absolute; width: 50%; z-index: 100; bottom: 0; right: 0; } + +.parallax .parallax-bottom-right:focus ~ .parallax-content, .parallax .parallax-bottom-right:hover ~ .parallax-content { transform: perspective(1000px) rotateX(-3deg) rotateY(3deg); } + +.parallax .parallax-bottom-right:focus ~ .parallax-content::before, .parallax .parallax-bottom-right:hover ~ .parallax-content::before { background: linear-gradient(-45deg, rgba(255, 255, 255, 0.35) 0%, transparent 50%); } + +.parallax .parallax-bottom-right:focus ~ .parallax-content .parallax-front, .parallax .parallax-bottom-right:hover ~ .parallax-content .parallax-front { transform: translate3d(-4.5px, -4.5px, 50px) scale(0.95); } + +.progress { -webkit-appearance: none; -moz-appearance: none; appearance: none; background: #f0f1f4; border: 0; border-radius: 0.1rem; color: #3085EE; height: 0.2rem; position: relative; width: 100%; } + +.progress::-webkit-progress-bar { background: transparent; border-radius: 0.1rem; } + +.progress::-webkit-progress-value { background: #3085EE; border-radius: 0.1rem; } + +.progress::-moz-progress-bar { background: #3085EE; border-radius: 0.1rem; } + +.progress:indeterminate { animation: progress-indeterminate 1.5s linear infinite; background: #f0f1f4 linear-gradient(to right, #3085EE 30%, #f0f1f4 30%) top left/150% 150% no-repeat; } + +.progress:indeterminate::-moz-progress-bar { background: transparent; } + +@keyframes progress-indeterminate { 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } } + +.slider { -webkit-appearance: none; -moz-appearance: none; appearance: none; background: transparent; display: block; width: 100%; height: 1.2rem; } + +.slider:focus { box-shadow: 0 0 0 0.1rem rgba(48, 133, 238, 0.2); outline: none; } + +.slider.tooltip:not([data-tooltip])::after { content: attr(value); } + +.slider::-webkit-slider-thumb { -webkit-appearance: none; background: #3085EE; border: 0; border-radius: 50%; height: 0.6rem; margin-top: -0.25rem; transition: transform .2s; width: 0.6rem; } + +.slider::-moz-range-thumb { background: #3085EE; border: 0; border-radius: 50%; height: 0.6rem; transition: transform .2s; width: 0.6rem; } + +.slider::-ms-thumb { background: #3085EE; border: 0; border-radius: 50%; height: 0.6rem; transition: transform .2s; width: 0.6rem; } + +.slider:active::-webkit-slider-thumb { transform: scale(1.25); } + +.slider:active::-moz-range-thumb { transform: scale(1.25); } + +.slider:active::-ms-thumb { transform: scale(1.25); } + +.slider:disabled::-webkit-slider-thumb, .slider.disabled::-webkit-slider-thumb { background: #e7e9ed; transform: scale(1); } + +.slider:disabled::-moz-range-thumb, .slider.disabled::-moz-range-thumb { background: #e7e9ed; transform: scale(1); } + +.slider:disabled::-ms-thumb, .slider.disabled::-ms-thumb { background: #e7e9ed; transform: scale(1); } + +.slider::-webkit-slider-runnable-track { background: #f0f1f4; border-radius: 0.1rem; height: 0.1rem; width: 100%; } + +.slider::-moz-range-track { background: #f0f1f4; border-radius: 0.1rem; height: 0.1rem; width: 100%; } + +.slider::-ms-track { background: #f0f1f4; border-radius: 0.1rem; height: 0.1rem; width: 100%; } + +.slider::-ms-fill-lower { background: #3085EE; } + +.timeline .timeline-item { display: -ms-flexbox; display: flex; margin-bottom: 1.2rem; position: relative; } + +.timeline .timeline-item::before { background: #e7e9ed; content: ""; height: 100%; left: 11px; position: absolute; top: 1.2rem; width: 2px; } + +.timeline .timeline-item .timeline-left { -ms-flex: 0 0 auto; flex: 0 0 auto; } + +.timeline .timeline-item .timeline-content { -ms-flex: 1 1 auto; flex: 1 1 auto; padding: 2px 0 2px 0.8rem; } + +.timeline .timeline-item .timeline-icon { -ms-flex-align: center; align-items: center; border-radius: 50%; color: #fff; display: -ms-flexbox; display: flex; height: 1.2rem; -ms-flex-pack: center; justify-content: center; text-align: center; width: 1.2rem; } + +.timeline .timeline-item .timeline-icon::before { border: 0.1rem solid #3085EE; border-radius: 50%; content: ""; display: block; height: 0.4rem; left: 0.4rem; position: absolute; top: 0.4rem; width: 0.4rem; } + +.timeline .timeline-item .timeline-icon.icon-lg { background: #3085EE; line-height: 1.2rem; } + +.timeline .timeline-item .timeline-icon.icon-lg::before { content: none; } + +.viewer-360 { -ms-flex-align: center; align-items: center; display: -ms-flexbox; display: flex; -ms-flex-direction: column; flex-direction: column; } + +.viewer-360 .viewer-slider[max='36'][value='1'] + .viewer-image { background-position-y: 0%; } + +.viewer-360 .viewer-slider[max='36'][value='2'] + .viewer-image { background-position-y: 2.8571428571%; } + +.viewer-360 .viewer-slider[max='36'][value='3'] + .viewer-image { background-position-y: 5.7142857143%; } + +.viewer-360 .viewer-slider[max='36'][value='4'] + .viewer-image { background-position-y: 8.5714285714%; } + +.viewer-360 .viewer-slider[max='36'][value='5'] + .viewer-image { background-position-y: 11.4285714286%; } + +.viewer-360 .viewer-slider[max='36'][value='6'] + .viewer-image { background-position-y: 14.2857142857%; } + +.viewer-360 .viewer-slider[max='36'][value='7'] + .viewer-image { background-position-y: 17.1428571429%; } + +.viewer-360 .viewer-slider[max='36'][value='8'] + .viewer-image { background-position-y: 20%; } + +.viewer-360 .viewer-slider[max='36'][value='9'] + .viewer-image { background-position-y: 22.8571428571%; } + +.viewer-360 .viewer-slider[max='36'][value='10'] + .viewer-image { background-position-y: 25.7142857143%; } + +.viewer-360 .viewer-slider[max='36'][value='11'] + .viewer-image { background-position-y: 28.5714285714%; } + +.viewer-360 .viewer-slider[max='36'][value='12'] + .viewer-image { background-position-y: 31.4285714286%; } + +.viewer-360 .viewer-slider[max='36'][value='13'] + .viewer-image { background-position-y: 34.2857142857%; } + +.viewer-360 .viewer-slider[max='36'][value='14'] + .viewer-image { background-position-y: 37.1428571429%; } + +.viewer-360 .viewer-slider[max='36'][value='15'] + .viewer-image { background-position-y: 40%; } + +.viewer-360 .viewer-slider[max='36'][value='16'] + .viewer-image { background-position-y: 42.8571428571%; } + +.viewer-360 .viewer-slider[max='36'][value='17'] + .viewer-image { background-position-y: 45.7142857143%; } + +.viewer-360 .viewer-slider[max='36'][value='18'] + .viewer-image { background-position-y: 48.5714285714%; } + +.viewer-360 .viewer-slider[max='36'][value='19'] + .viewer-image { background-position-y: 51.4285714286%; } + +.viewer-360 .viewer-slider[max='36'][value='20'] + .viewer-image { background-position-y: 54.2857142857%; } + +.viewer-360 .viewer-slider[max='36'][value='21'] + .viewer-image { background-position-y: 57.1428571429%; } + +.viewer-360 .viewer-slider[max='36'][value='22'] + .viewer-image { background-position-y: 60%; } + +.viewer-360 .viewer-slider[max='36'][value='23'] + .viewer-image { background-position-y: 62.8571428571%; } + +.viewer-360 .viewer-slider[max='36'][value='24'] + .viewer-image { background-position-y: 65.7142857143%; } + +.viewer-360 .viewer-slider[max='36'][value='25'] + .viewer-image { background-position-y: 68.5714285714%; } + +.viewer-360 .viewer-slider[max='36'][value='26'] + .viewer-image { background-position-y: 71.4285714286%; } + +.viewer-360 .viewer-slider[max='36'][value='27'] + .viewer-image { background-position-y: 74.2857142857%; } + +.viewer-360 .viewer-slider[max='36'][value='28'] + .viewer-image { background-position-y: 77.1428571429%; } + +.viewer-360 .viewer-slider[max='36'][value='29'] + .viewer-image { background-position-y: 80%; } + +.viewer-360 .viewer-slider[max='36'][value='30'] + .viewer-image { background-position-y: 82.8571428571%; } + +.viewer-360 .viewer-slider[max='36'][value='31'] + .viewer-image { background-position-y: 85.7142857143%; } + +.viewer-360 .viewer-slider[max='36'][value='32'] + .viewer-image { background-position-y: 88.5714285714%; } + +.viewer-360 .viewer-slider[max='36'][value='33'] + .viewer-image { background-position-y: 91.4285714286%; } + +.viewer-360 .viewer-slider[max='36'][value='34'] + .viewer-image { background-position-y: 94.2857142857%; } + +.viewer-360 .viewer-slider[max='36'][value='35'] + .viewer-image { background-position-y: 97.1428571429%; } + +.viewer-360 .viewer-slider[max='36'][value='36'] + .viewer-image { background-position-y: 100%; } + +.viewer-360 .viewer-slider { cursor: ew-resize; margin: 1rem; -ms-flex-order: 2; order: 2; width: 60%; } + +.viewer-360 .viewer-image { background-position-y: 0; background-repeat: no-repeat; background-size: 100%; max-width: 100%; -ms-flex-order: 1; order: 1; } + +/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BlY3RyZS1leHAuY3NzIiwic291cmNlcyI6WyJzcGVjdHJlLWV4cC5zY3NzIiwidGhlbWUvX3ZhcmlhYmxlcy5zY3NzIiwic3BlY3RyZS9fdmFyaWFibGVzLnNjc3MiLCJzcGVjdHJlL19taXhpbnMuc2NzcyIsInNwZWN0cmUvbWl4aW5zL19hdmF0YXIuc2NzcyIsInNwZWN0cmUvbWl4aW5zL19idXR0b24uc2NzcyIsInNwZWN0cmUvbWl4aW5zL19jbGVhcmZpeC5zY3NzIiwic3BlY3RyZS9taXhpbnMvX2NvbG9yLnNjc3MiLCJzcGVjdHJlL21peGlucy9fbGFiZWwuc2NzcyIsInNwZWN0cmUvbWl4aW5zL19wb3NpdGlvbi5zY3NzIiwic3BlY3RyZS9taXhpbnMvX3NoYWRvdy5zY3NzIiwic3BlY3RyZS9taXhpbnMvX3RleHQuc2NzcyIsInNwZWN0cmUvbWl4aW5zL190b2FzdC5zY3NzIiwic3BlY3RyZS9fYXV0b2NvbXBsZXRlLnNjc3MiLCJzcGVjdHJlL19jYWxlbmRhcnMuc2NzcyIsInNwZWN0cmUvX2Nhcm91c2Vscy5zY3NzIiwic3BlY3RyZS9fY29tcGFyaXNvbi1zbGlkZXJzLnNjc3MiLCJzcGVjdHJlL19maWx0ZXJzLnNjc3MiLCJzcGVjdHJlL19tZXRlcnMuc2NzcyIsInNwZWN0cmUvX29mZi1jYW52YXMuc2NzcyIsInNwZWN0cmUvX3BhcmFsbGF4LnNjc3MiLCJzcGVjdHJlL19wcm9ncmVzcy5zY3NzIiwic3BlY3RyZS9fc2xpZGVycy5zY3NzIiwic3BlY3RyZS9fdGltZWxpbmVzLnNjc3MiLCJzcGVjdHJlL192aWV3ZXItMzYwLnNjc3MiXSwic291cmNlc0NvbnRlbnQiOlsiLy8gVmFyaWFibGVzIGFuZCBtaXhpbnNcbkBpbXBvcnQgXCJ0aGVtZS92YXJpYWJsZXNcIjtcbkBpbXBvcnQgXCJzcGVjdHJlL3ZhcmlhYmxlc1wiO1xuQGltcG9ydCBcInNwZWN0cmUvbWl4aW5zXCI7XG5cbi8qISBTcGVjdHJlLmNzcyBFeHBlcmltZW50YWxzIHYjeyR2ZXJzaW9ufSB8IE1JVCBMaWNlbnNlIHwgZ2l0aHViLmNvbS9waWN0dXJlcGFuMi9zcGVjdHJlICovXG4vLyBFeHBlcmltZW50YWxzXG5AaW1wb3J0IFwic3BlY3RyZS9hdXRvY29tcGxldGVcIjtcbkBpbXBvcnQgXCJzcGVjdHJlL2NhbGVuZGFyc1wiO1xuQGltcG9ydCBcInNwZWN0cmUvY2Fyb3VzZWxzXCI7XG5AaW1wb3J0IFwic3BlY3RyZS9jb21wYXJpc29uLXNsaWRlcnNcIjtcbkBpbXBvcnQgXCJzcGVjdHJlL2ZpbHRlcnNcIjtcbkBpbXBvcnQgXCJzcGVjdHJlL21ldGVyc1wiO1xuQGltcG9ydCBcInNwZWN0cmUvb2ZmLWNhbnZhc1wiO1xuQGltcG9ydCBcInNwZWN0cmUvcGFyYWxsYXhcIjtcbkBpbXBvcnQgXCJzcGVjdHJlL3Byb2dyZXNzXCI7XG5AaW1wb3J0IFwic3BlY3RyZS9zbGlkZXJzXCI7XG5AaW1wb3J0IFwic3BlY3RyZS90aW1lbGluZXNcIjtcbkBpbXBvcnQgXCJzcGVjdHJlL3ZpZXdlci0zNjBcIjtcbiIsIi8vIFNwZWN0cmUgT3ZlcnJpZGVzXG4kcHJpbWFyeS1jb2xvcjogIzMwODVFRSAhZGVmYXVsdDtcbiRkYXJrLWNvbG9yOiAjNDU0ZDVkICFkZWZhdWx0O1xuJGxpZ2h0LWNvbG9yOiAjZmZmICFkZWZhdWx0O1xuJGdyYXktY29sb3I6IGxpZ2h0ZW4oJGRhcmstY29sb3IsIDQwJSkgIWRlZmF1bHQ7XG4kZ3JheS1jb2xvci1kYXJrOiBkYXJrZW4oJGdyYXktY29sb3IsIDI1JSkgIWRlZmF1bHQ7XG4kYm9yZGVyLWNvbG9yOiBsaWdodGVuKCRkYXJrLWNvbG9yLCA2MCUpICFkZWZhdWx0O1xuJGJnLWNvbG9yOiBsaWdodGVuKCRkYXJrLWNvbG9yLCA2NiUpICFkZWZhdWx0O1xuJGJvZHktZm9udC1jb2xvcjogbGlnaHRlbigkZGFyay1jb2xvciwgNSUpICFkZWZhdWx0O1xuXG4vLyBMYXlvdXRcbiRob3Jpei1wYWRkaW5nOiAxcmVtO1xuJHZlcnQtcGFkZGluZzogMnJlbSAwIDJyZW07XG5cbi8vIEZvbnRzXG4kbWluLXJlc3BvbnNpdmUtZm9udC1zaXplOiAxNnB4O1xuXG4vLyBIZWFkZXJcbiRoZWFkZXItaGVpZ2h0LWxhcmdlOiA0cmVtO1xuJGhlYWRlci1oZWlnaHQtc21hbGw6IDIuM3JlbTtcbiRoZWFkZXItY29sb3ItZGFyazogIzIyMjtcbiRoZWFkZXItdGV4dC1saWdodDogJGxpZ2h0LWNvbG9yO1xuJGhlYWRlci10ZXh0LWRhcms6IGRhcmtlbigkZGFyay1jb2xvciwgMTUlKTtcblxuLy8gRHJvcGRvd24gTWVudVxuJGRyb3BtZW51LWJnOiAkbGlnaHQtY29sb3I7XG4kZHJvcG1lbnUtaG92ZXItdGV4dDogJHByaW1hcnktY29sb3I7XG4kZHJvcG1lbnUtaG9yaXotcGFkZGluZzogMjBweDtcbiRkcm9wbWVudS12ZXJ0LXBhZGRpbmc6IDdweDtcbiRkcm9wbWVudS1jaGlsZC1wYWRkaW5nOiAxMHB4O1xuJGRyb3BtZW51LXNoYWRvdzogMCAzcHggNXB4IHJnYmEoMCwgMCwgMCwgMC4xKTtcblxuLy8gTW9iaWxlIE1lbnVcbiRtb2JpbGUtY29sb3ItbWFpbjogJHByaW1hcnktY29sb3I7XG4kbW9iaWxlLWNvbG9yLWFjdGl2ZTogI0ZGRjtcbiRtb2JpbGUtY29sb3ItbGluazogI0ZGRjtcbiRtb2JpbGUtYnV0dG9uLWhlaWdodDogMjRweDtcbiRtb2JpbGUtYnV0dG9uLXdpZHRoOiAyOHB4OyIsIi8vIENvcmUgdmFyaWFibGVzXG4kdmVyc2lvbjogXCIwLjUuOFwiO1xuXG4vLyBDb3JlIGZlYXR1cmVzXG4kcnRsOiBmYWxzZSAhZGVmYXVsdDtcblxuLy8gQ29yZSBjb2xvcnNcbiRwcmltYXJ5LWNvbG9yOiAjNTc1NWQ5ICFkZWZhdWx0O1xuJHByaW1hcnktY29sb3ItZGFyazogZGFya2VuKCRwcmltYXJ5LWNvbG9yLCAzJSkgIWRlZmF1bHQ7XG4kcHJpbWFyeS1jb2xvci1saWdodDogbGlnaHRlbigkcHJpbWFyeS1jb2xvciwgMyUpICFkZWZhdWx0O1xuJHNlY29uZGFyeS1jb2xvcjogbGlnaHRlbigkcHJpbWFyeS1jb2xvciwgMzcuNSUpICFkZWZhdWx0O1xuJHNlY29uZGFyeS1jb2xvci1kYXJrOiBkYXJrZW4oJHNlY29uZGFyeS1jb2xvciwgMyUpICFkZWZhdWx0O1xuJHNlY29uZGFyeS1jb2xvci1saWdodDogbGlnaHRlbigkc2Vjb25kYXJ5LWNvbG9yLCAzJSkgIWRlZmF1bHQ7XG5cbi8vIEdyYXkgY29sb3JzXG4kZGFyay1jb2xvcjogIzMwMzc0MiAhZGVmYXVsdDtcbiRsaWdodC1jb2xvcjogI2ZmZiAhZGVmYXVsdDtcbiRncmF5LWNvbG9yOiBsaWdodGVuKCRkYXJrLWNvbG9yLCA1NSUpICFkZWZhdWx0O1xuJGdyYXktY29sb3ItZGFyazogZGFya2VuKCRncmF5LWNvbG9yLCAzMCUpICFkZWZhdWx0O1xuJGdyYXktY29sb3ItbGlnaHQ6IGxpZ2h0ZW4oJGdyYXktY29sb3IsIDIwJSkgIWRlZmF1bHQ7XG5cbiRib3JkZXItY29sb3I6IGxpZ2h0ZW4oJGRhcmstY29sb3IsIDY1JSkgIWRlZmF1bHQ7XG4kYm9yZGVyLWNvbG9yLWRhcms6IGRhcmtlbigkYm9yZGVyLWNvbG9yLCAxMCUpICFkZWZhdWx0O1xuJGJvcmRlci1jb2xvci1saWdodDogbGlnaHRlbigkYm9yZGVyLWNvbG9yLCA4JSkgIWRlZmF1bHQ7XG4kYmctY29sb3I6IGxpZ2h0ZW4oJGRhcmstY29sb3IsIDc1JSkgIWRlZmF1bHQ7XG4kYmctY29sb3ItZGFyazogZGFya2VuKCRiZy1jb2xvciwgMyUpICFkZWZhdWx0O1xuJGJnLWNvbG9yLWxpZ2h0OiAkbGlnaHQtY29sb3IgIWRlZmF1bHQ7XG5cbi8vIENvbnRyb2wgY29sb3JzXG4kc3VjY2Vzcy1jb2xvcjogIzMyYjY0MyAhZGVmYXVsdDtcbiR3YXJuaW5nLWNvbG9yOiAjZmZiNzAwICFkZWZhdWx0O1xuJGVycm9yLWNvbG9yOiAjZTg1NjAwICFkZWZhdWx0O1xuXG4vLyBPdGhlciBjb2xvcnNcbiRjb2RlLWNvbG9yOiAjZDczZTQ4ICFkZWZhdWx0O1xuJGhpZ2hsaWdodC1jb2xvcjogI2ZmZTliMyAhZGVmYXVsdDtcbiRib2R5LWJnOiAkYmctY29sb3ItbGlnaHQgIWRlZmF1bHQ7XG4kYm9keS1mb250LWNvbG9yOiBsaWdodGVuKCRkYXJrLWNvbG9yLCA1JSkgIWRlZmF1bHQ7XG4kbGluay1jb2xvcjogJHByaW1hcnktY29sb3IgIWRlZmF1bHQ7XG4kbGluay1jb2xvci1kYXJrOiBkYXJrZW4oJGxpbmstY29sb3IsIDEwJSkgIWRlZmF1bHQ7XG4kbGluay1jb2xvci1saWdodDogbGlnaHRlbigkbGluay1jb2xvciwgMTAlKSAhZGVmYXVsdDtcblxuLy8gRm9udHNcbi8vIENyZWRpdDogaHR0cHM6Ly93d3cuc21hc2hpbmdtYWdhemluZS5jb20vMjAxNS8xMS91c2luZy1zeXN0ZW0tdWktZm9udHMtcHJhY3RpY2FsLWd1aWRlL1xuJGJhc2UtZm9udC1mYW1pbHk6IC1hcHBsZS1zeXN0ZW0sIHN5c3RlbS11aSwgQmxpbmtNYWNTeXN0ZW1Gb250LCBcIlNlZ29lIFVJXCIsIFJvYm90byAhZGVmYXVsdDtcbiRtb25vLWZvbnQtZmFtaWx5OiBcIlNGIE1vbm9cIiwgXCJTZWdvZSBVSSBNb25vXCIsIFwiUm9ib3RvIE1vbm9cIiwgTWVubG8sIENvdXJpZXIsIG1vbm9zcGFjZSAhZGVmYXVsdDtcbiRmYWxsYmFjay1mb250LWZhbWlseTogXCJIZWx2ZXRpY2EgTmV1ZVwiLCBzYW5zLXNlcmlmICFkZWZhdWx0O1xuJGNqay16aC1oYW5zLWZvbnQtZmFtaWx5OiAkYmFzZS1mb250LWZhbWlseSwgXCJQaW5nRmFuZyBTQ1wiLCBcIkhpcmFnaW5vIFNhbnMgR0JcIiwgXCJNaWNyb3NvZnQgWWFIZWlcIiwgJGZhbGxiYWNrLWZvbnQtZmFtaWx5ICFkZWZhdWx0O1xuJGNqay16aC1oYW50LWZvbnQtZmFtaWx5OiAkYmFzZS1mb250LWZhbWlseSwgXCJQaW5nRmFuZyBUQ1wiLCBcIkhpcmFnaW5vIFNhbnMgQ05TXCIsIFwiTWljcm9zb2Z0IEpoZW5nSGVpXCIsICRmYWxsYmFjay1mb250LWZhbWlseSAhZGVmYXVsdDtcbiRjamstanAtZm9udC1mYW1pbHk6ICRiYXNlLWZvbnQtZmFtaWx5LCBcIkhpcmFnaW5vIFNhbnNcIiwgXCJIaXJhZ2lubyBLYWt1IEdvdGhpYyBQcm9cIiwgXCJZdSBHb3RoaWNcIiwgWXVHb3RoaWMsIE1laXJ5bywgJGZhbGxiYWNrLWZvbnQtZmFtaWx5ICFkZWZhdWx0O1xuJGNqay1rby1mb250LWZhbWlseTogJGJhc2UtZm9udC1mYW1pbHksIFwiTWFsZ3VuIEdvdGhpY1wiLCAkZmFsbGJhY2stZm9udC1mYW1pbHkgIWRlZmF1bHQ7XG4kYm9keS1mb250LWZhbWlseTogJGJhc2UtZm9udC1mYW1pbHksICRmYWxsYmFjay1mb250LWZhbWlseSAhZGVmYXVsdDtcblxuLy8gVW5pdCBzaXplc1xuJHVuaXQtbzogLjA1cmVtICFkZWZhdWx0O1xuJHVuaXQtaDogLjFyZW0gIWRlZmF1bHQ7XG4kdW5pdC0xOiAuMnJlbSAhZGVmYXVsdDtcbiR1bml0LTI6IC40cmVtICFkZWZhdWx0O1xuJHVuaXQtMzogLjZyZW0gIWRlZmF1bHQ7XG4kdW5pdC00OiAuOHJlbSAhZGVmYXVsdDtcbiR1bml0LTU6IDFyZW0gIWRlZmF1bHQ7XG4kdW5pdC02OiAxLjJyZW0gIWRlZmF1bHQ7XG4kdW5pdC03OiAxLjRyZW0gIWRlZmF1bHQ7XG4kdW5pdC04OiAxLjZyZW0gIWRlZmF1bHQ7XG4kdW5pdC05OiAxLjhyZW0gIWRlZmF1bHQ7XG4kdW5pdC0xMDogMnJlbSAhZGVmYXVsdDtcbiR1bml0LTEyOiAyLjRyZW0gIWRlZmF1bHQ7XG4kdW5pdC0xNjogMy4ycmVtICFkZWZhdWx0O1xuXG4vLyBGb250IHNpemVzXG4kaHRtbC1mb250LXNpemU6IDIwcHggIWRlZmF1bHQ7XG4kaHRtbC1saW5lLWhlaWdodDogMS41ICFkZWZhdWx0O1xuJGZvbnQtc2l6ZTogLjhyZW0gIWRlZmF1bHQ7XG4kZm9udC1zaXplLXNtOiAuN3JlbSAhZGVmYXVsdDtcbiRmb250LXNpemUtbGc6IC45cmVtICFkZWZhdWx0O1xuJGxpbmUtaGVpZ2h0OiAxLjJyZW0gIWRlZmF1bHQ7XG5cbi8vIFNpemVzXG4kbGF5b3V0LXNwYWNpbmc6ICR1bml0LTIgIWRlZmF1bHQ7XG4kbGF5b3V0LXNwYWNpbmctc206ICR1bml0LTEgIWRlZmF1bHQ7XG4kbGF5b3V0LXNwYWNpbmctbGc6ICR1bml0LTQgIWRlZmF1bHQ7XG4kYm9yZGVyLXJhZGl1czogJHVuaXQtaCAhZGVmYXVsdDtcbiRib3JkZXItd2lkdGg6ICR1bml0LW8gIWRlZmF1bHQ7XG4kYm9yZGVyLXdpZHRoLWxnOiAkdW5pdC1oICFkZWZhdWx0O1xuJGNvbnRyb2wtc2l6ZTogJHVuaXQtOSAhZGVmYXVsdDtcbiRjb250cm9sLXNpemUtc206ICR1bml0LTcgIWRlZmF1bHQ7XG4kY29udHJvbC1zaXplLWxnOiAkdW5pdC0xMCAhZGVmYXVsdDtcbiRjb250cm9sLXBhZGRpbmcteDogJHVuaXQtMiAhZGVmYXVsdDtcbiRjb250cm9sLXBhZGRpbmcteC1zbTogJHVuaXQtMiAqIC43NSAhZGVmYXVsdDtcbiRjb250cm9sLXBhZGRpbmcteC1sZzogJHVuaXQtMiAqIDEuNSAhZGVmYXVsdDtcbiRjb250cm9sLXBhZGRpbmcteTogKCRjb250cm9sLXNpemUgLSAkbGluZS1oZWlnaHQpIC8gMiAtICRib3JkZXItd2lkdGggIWRlZmF1bHQ7XG4kY29udHJvbC1wYWRkaW5nLXktc206ICgkY29udHJvbC1zaXplLXNtIC0gJGxpbmUtaGVpZ2h0KSAvIDIgLSAkYm9yZGVyLXdpZHRoICFkZWZhdWx0O1xuJGNvbnRyb2wtcGFkZGluZy15LWxnOiAoJGNvbnRyb2wtc2l6ZS1sZyAtICRsaW5lLWhlaWdodCkgLyAyIC0gJGJvcmRlci13aWR0aCAhZGVmYXVsdDtcbiRjb250cm9sLWljb24tc2l6ZTogLjhyZW0gIWRlZmF1bHQ7XG5cbiRjb250cm9sLXdpZHRoLXhzOiAxODBweCAhZGVmYXVsdDtcbiRjb250cm9sLXdpZHRoLXNtOiAzMjBweCAhZGVmYXVsdDtcbiRjb250cm9sLXdpZHRoLW1kOiA2NDBweCAhZGVmYXVsdDtcbiRjb250cm9sLXdpZHRoLWxnOiA5NjBweCAhZGVmYXVsdDtcbiRjb250cm9sLXdpZHRoLXhsOiAxMjgwcHggIWRlZmF1bHQ7XG5cbi8vIFJlc3BvbnNpdmUgYnJlYWtwb2ludHNcbiRzaXplLXhzOiA0ODBweCAhZGVmYXVsdDtcbiRzaXplLXNtOiA2MDBweCAhZGVmYXVsdDtcbiRzaXplLW1kOiA4NDBweCAhZGVmYXVsdDtcbiRzaXplLWxnOiA5NjBweCAhZGVmYXVsdDtcbiRzaXplLXhsOiAxMjgwcHggIWRlZmF1bHQ7XG4kc2l6ZS0yeDogMTQ0MHB4ICFkZWZhdWx0O1xuXG4kcmVzcG9uc2l2ZS1icmVha3BvaW50OiAkc2l6ZS14cyAhZGVmYXVsdDtcblxuLy8gWi1pbmRleFxuJHppbmRleC0wOiAxICFkZWZhdWx0O1xuJHppbmRleC0xOiAxMDAgIWRlZmF1bHQ7XG4kemluZGV4LTI6IDIwMCAhZGVmYXVsdDtcbiR6aW5kZXgtMzogMzAwICFkZWZhdWx0O1xuJHppbmRleC00OiA0MDAgIWRlZmF1bHQ7XG4iLCIvLyBNaXhpbnNcbkBpbXBvcnQgXCJtaXhpbnMvYXZhdGFyXCI7XG5AaW1wb3J0IFwibWl4aW5zL2J1dHRvblwiO1xuQGltcG9ydCBcIm1peGlucy9jbGVhcmZpeFwiO1xuQGltcG9ydCBcIm1peGlucy9jb2xvclwiO1xuQGltcG9ydCBcIm1peGlucy9sYWJlbFwiO1xuQGltcG9ydCBcIm1peGlucy9wb3NpdGlvblwiO1xuQGltcG9ydCBcIm1peGlucy9zaGFkb3dcIjtcbkBpbXBvcnQgXCJtaXhpbnMvdGV4dFwiO1xuQGltcG9ydCBcIm1peGlucy90b2FzdFwiOyIsIi8vIEF2YXRhciBtaXhpblxuQG1peGluIGF2YXRhci1iYXNlKCRzaXplOiAkdW5pdC04KSB7XG4gIGZvbnQtc2l6ZTogJHNpemUgLyAyO1xuICBoZWlnaHQ6ICRzaXplO1xuICB3aWR0aDogJHNpemU7XG59XG4iLCIvLyBCdXR0b24gdmFyaWFudCBtaXhpblxuQG1peGluIGJ1dHRvbi12YXJpYW50KCRjb2xvcjogJHByaW1hcnktY29sb3IpIHtcbiAgYmFja2dyb3VuZDogJGNvbG9yO1xuICBib3JkZXItY29sb3I6IGRhcmtlbigkY29sb3IsIDMlKTtcbiAgY29sb3I6ICRsaWdodC1jb2xvcjtcbiAgJjpmb2N1cyB7XG4gICAgQGluY2x1ZGUgY29udHJvbC1zaGFkb3coJGNvbG9yKTtcbiAgfVxuICAmOmZvY3VzLFxuICAmOmhvdmVyIHtcbiAgICBiYWNrZ3JvdW5kOiBkYXJrZW4oJGNvbG9yLCAyJSk7XG4gICAgYm9yZGVyLWNvbG9yOiBkYXJrZW4oJGNvbG9yLCA1JSk7XG4gICAgY29sb3I6ICRsaWdodC1jb2xvcjtcbiAgfVxuICAmOmFjdGl2ZSxcbiAgJi5hY3RpdmUge1xuICAgIGJhY2tncm91bmQ6IGRhcmtlbigkY29sb3IsIDclKTtcbiAgICBib3JkZXItY29sb3I6IGRhcmtlbigkY29sb3IsIDEwJSk7XG4gICAgY29sb3I6ICRsaWdodC1jb2xvcjtcbiAgfVxuICAmLmxvYWRpbmcge1xuICAgICY6OmFmdGVyIHtcbiAgICAgIGJvcmRlci1ib3R0b20tY29sb3I6ICRsaWdodC1jb2xvcjtcbiAgICAgIGJvcmRlci1sZWZ0LWNvbG9yOiAkbGlnaHQtY29sb3I7XG4gICAgfVxuICB9XG59XG5cbkBtaXhpbiBidXR0b24tb3V0bGluZS12YXJpYW50KCRjb2xvcjogJHByaW1hcnktY29sb3IpIHtcbiAgYmFja2dyb3VuZDogJGxpZ2h0LWNvbG9yO1xuICBib3JkZXItY29sb3I6ICRjb2xvcjtcbiAgY29sb3I6ICRjb2xvcjtcbiAgJjpmb2N1cyB7XG4gICAgQGluY2x1ZGUgY29udHJvbC1zaGFkb3coJGNvbG9yKTtcbiAgfVxuICAmOmZvY3VzLFxuICAmOmhvdmVyIHtcbiAgICBiYWNrZ3JvdW5kOiBsaWdodGVuKCRjb2xvciwgNTAlKTtcbiAgICBib3JkZXItY29sb3I6IGRhcmtlbigkY29sb3IsIDIlKTtcbiAgICBjb2xvcjogJGNvbG9yO1xuICB9XG4gICY6YWN0aXZlLFxuICAmLmFjdGl2ZSB7XG4gICAgYmFja2dyb3VuZDogJGNvbG9yO1xuICAgIGJvcmRlci1jb2xvcjogZGFya2VuKCRjb2xvciwgNSUpO1xuICAgIGNvbG9yOiAkbGlnaHQtY29sb3I7XG4gIH1cbiAgJi5sb2FkaW5nIHtcbiAgICAmOjphZnRlciB7XG4gICAgICBib3JkZXItYm90dG9tLWNvbG9yOiAkY29sb3I7XG4gICAgICBib3JkZXItbGVmdC1jb2xvcjogJGNvbG9yO1xuICAgIH1cbiAgfVxufVxuIiwiLy8gQ2xlYXJmaXggbWl4aW5cbkBtaXhpbiBjbGVhcmZpeCgpIHtcbiAgJjo6YWZ0ZXIge1xuICAgIGNsZWFyOiBib3RoO1xuICAgIGNvbnRlbnQ6IFwiXCI7XG4gICAgZGlzcGxheTogdGFibGU7XG4gIH1cbn1cbiIsIi8vIEJhY2tncm91bmQgY29sb3IgdXRpbGl0eSBtaXhpblxuQG1peGluIGJnLWNvbG9yLXZhcmlhbnQoJG5hbWU6IFwiLmJnLXByaW1hcnlcIiwgJGNvbG9yOiAkcHJpbWFyeS1jb2xvcikge1xuICAjeyRuYW1lfSB7XG4gICAgYmFja2dyb3VuZDogJGNvbG9yICFpbXBvcnRhbnQ7XG5cbiAgICBAaWYgKGxpZ2h0bmVzcygkY29sb3IpIDwgNjApIHtcbiAgICAgIGNvbG9yOiAkbGlnaHQtY29sb3I7XG4gICAgfVxuICB9XG59XG5cbi8vIFRleHQgY29sb3IgdXRpbGl0eSBtaXhpblxuQG1peGluIHRleHQtY29sb3ItdmFyaWFudCgkbmFtZTogXCIudGV4dC1wcmltYXJ5XCIsICRjb2xvcjogJHByaW1hcnktY29sb3IpIHtcbiAgI3skbmFtZX0ge1xuICAgIGNvbG9yOiAkY29sb3IgIWltcG9ydGFudDtcbiAgfVxuXG4gIGEjeyRuYW1lfSB7XG4gICAgJjpmb2N1cyxcbiAgICAmOmhvdmVyIHtcbiAgICAgIGNvbG9yOiBkYXJrZW4oJGNvbG9yLCA1JSk7XG4gICAgfVxuICAgICY6dmlzaXRlZCB7XG4gICAgICBjb2xvcjogbGlnaHRlbigkY29sb3IsIDUlKTtcbiAgICB9XG4gIH1cbn1cbiIsIi8vIExhYmVsIGJhc2Ugc3R5bGVcbkBtaXhpbiBsYWJlbC1iYXNlKCkge1xuICBib3JkZXItcmFkaXVzOiAkYm9yZGVyLXJhZGl1cztcbiAgbGluZS1oZWlnaHQ6IDEuMjU7XG4gIHBhZGRpbmc6IC4xcmVtIC4ycmVtO1xufVxuXG5AbWl4aW4gbGFiZWwtdmFyaWFudCgkY29sb3I6ICRsaWdodC1jb2xvciwgJGJnLWNvbG9yOiAkcHJpbWFyeS1jb2xvcikge1xuICBiYWNrZ3JvdW5kOiAkYmctY29sb3I7XG4gIGNvbG9yOiAkY29sb3I7XG59XG4iLCIvLyBNYXJnaW4gdXRpbGl0eSBtaXhpblxuQG1peGluIG1hcmdpbi12YXJpYW50KCRpZDogMSwgJHNpemU6ICR1bml0LTEpIHtcbiAgLm0tI3skaWR9IHtcbiAgICBtYXJnaW46ICRzaXplICFpbXBvcnRhbnQ7XG4gIH1cblxuICAubWItI3skaWR9IHtcbiAgICBtYXJnaW4tYm90dG9tOiAkc2l6ZSAhaW1wb3J0YW50O1xuICB9XG5cbiAgLm1sLSN7JGlkfSB7XG4gICAgbWFyZ2luLWxlZnQ6ICRzaXplICFpbXBvcnRhbnQ7XG4gIH1cblxuICAubXItI3skaWR9IHtcbiAgICBtYXJnaW4tcmlnaHQ6ICRzaXplICFpbXBvcnRhbnQ7XG4gIH1cblxuICAubXQtI3skaWR9IHtcbiAgICBtYXJnaW4tdG9wOiAkc2l6ZSAhaW1wb3J0YW50O1xuICB9XG5cbiAgLm14LSN7JGlkfSB7XG4gICAgbWFyZ2luLWxlZnQ6ICRzaXplICFpbXBvcnRhbnQ7XG4gICAgbWFyZ2luLXJpZ2h0OiAkc2l6ZSAhaW1wb3J0YW50O1xuICB9XG5cbiAgLm15LSN7JGlkfSB7XG4gICAgbWFyZ2luLWJvdHRvbTogJHNpemUgIWltcG9ydGFudDtcbiAgICBtYXJnaW4tdG9wOiAkc2l6ZSAhaW1wb3J0YW50O1xuICB9XG59XG5cbi8vIFBhZGRpbmcgdXRpbGl0eSBtaXhpblxuQG1peGluIHBhZGRpbmctdmFyaWFudCgkaWQ6IDEsICRzaXplOiAkdW5pdC0xKSB7XG4gIC5wLSN7JGlkfSB7XG4gICAgcGFkZGluZzogJHNpemUgIWltcG9ydGFudDtcbiAgfVxuXG4gIC5wYi0jeyRpZH0ge1xuICAgIHBhZGRpbmctYm90dG9tOiAkc2l6ZSAhaW1wb3J0YW50O1xuICB9XG5cbiAgLnBsLSN7JGlkfSB7XG4gICAgcGFkZGluZy1sZWZ0OiAkc2l6ZSAhaW1wb3J0YW50O1xuICB9XG5cbiAgLnByLSN7JGlkfSB7XG4gICAgcGFkZGluZy1yaWdodDogJHNpemUgIWltcG9ydGFudDtcbiAgfVxuXG4gIC5wdC0jeyRpZH0ge1xuICAgIHBhZGRpbmctdG9wOiAkc2l6ZSAhaW1wb3J0YW50O1xuICB9XG5cbiAgLnB4LSN7JGlkfSB7XG4gICAgcGFkZGluZy1sZWZ0OiAkc2l6ZSAhaW1wb3J0YW50O1xuICAgIHBhZGRpbmctcmlnaHQ6ICRzaXplICFpbXBvcnRhbnQ7XG4gIH1cbiAgXG4gIC5weS0jeyRpZH0ge1xuICAgIHBhZGRpbmctYm90dG9tOiAkc2l6ZSAhaW1wb3J0YW50O1xuICAgIHBhZGRpbmctdG9wOiAkc2l6ZSAhaW1wb3J0YW50O1xuICB9XG59XG4iLCIvLyBDb21wb25lbnQgZm9jdXMgc2hhZG93XG5AbWl4aW4gY29udHJvbC1zaGFkb3coJGNvbG9yOiAkcHJpbWFyeS1jb2xvcikge1xuICBib3gtc2hhZG93OiAwIDAgMCAuMXJlbSByZ2JhKCRjb2xvciwgLjIpO1xufVxuXG4vLyBTaGFkb3cgbWl4aW5cbkBtaXhpbiBzaGFkb3ctdmFyaWFudCgkb2Zmc2V0KSB7XG4gIGJveC1zaGFkb3c6IDAgJG9mZnNldCAoJG9mZnNldCArIC4wNXJlbSkgKiAyIHJnYmEoJGRhcmstY29sb3IsIC4zKTtcbn1cbiIsIi8vIFRleHQgRWxsaXBzaXNcbkBtaXhpbiB0ZXh0LWVsbGlwc2lzKCkge1xuICBvdmVyZmxvdzogaGlkZGVuO1xuICB0ZXh0LW92ZXJmbG93OiBlbGxpcHNpcztcbiAgd2hpdGUtc3BhY2U6IG5vd3JhcDtcbn1cbiIsIi8vIFRvYXN0IHZhcmlhbnQgbWl4aW5cbkBtaXhpbiB0b2FzdC12YXJpYW50KCRjb2xvcjogJGRhcmstY29sb3IpIHtcbiAgYmFja2dyb3VuZDogcmdiYSgkY29sb3IsIC45NSk7XG4gIGJvcmRlci1jb2xvcjogJGNvbG9yO1xufVxuIiwiLy8gQXV0b2NvbXBsZXRlXG4uZm9ybS1hdXRvY29tcGxldGUge1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG5cbiAgLmZvcm0tYXV0b2NvbXBsZXRlLWlucHV0IHtcbiAgICBhbGlnbi1jb250ZW50OiBmbGV4LXN0YXJ0O1xuICAgIGRpc3BsYXk6IGZsZXg7XG4gICAgZmxleC13cmFwOiB3cmFwO1xuICAgIGhlaWdodDogYXV0bztcbiAgICBtaW4taGVpZ2h0OiAkdW5pdC04O1xuICAgIHBhZGRpbmc6ICR1bml0LWg7XG5cbiAgICAmLmlzLWZvY3VzZWQge1xuICAgICAgQGluY2x1ZGUgY29udHJvbC1zaGFkb3coKTtcbiAgICAgIGJvcmRlci1jb2xvcjogJHByaW1hcnktY29sb3I7XG4gICAgfVxuXG4gICAgLmZvcm0taW5wdXQge1xuICAgICAgYm9yZGVyLWNvbG9yOiB0cmFuc3BhcmVudDtcbiAgICAgIGJveC1zaGFkb3c6IG5vbmU7XG4gICAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XG4gICAgICBmbGV4OiAxIDAgYXV0bztcbiAgICAgIGhlaWdodDogJHVuaXQtNjtcbiAgICAgIGxpbmUtaGVpZ2h0OiAkdW5pdC00O1xuICAgICAgbWFyZ2luOiAkdW5pdC1oO1xuICAgICAgd2lkdGg6IGF1dG87XG4gICAgfVxuICB9XG5cbiAgLm1lbnUge1xuICAgIGxlZnQ6IDA7XG4gICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgIHRvcDogMTAwJTtcbiAgICB3aWR0aDogMTAwJTtcbiAgfVxuXG4gICYuYXV0b2NvbXBsZXRlLW9uZWxpbmUge1xuICAgIC5mb3JtLWF1dG9jb21wbGV0ZS1pbnB1dCB7XG4gICAgICBmbGV4LXdyYXA6IG5vd3JhcDtcbiAgICAgIG92ZXJmbG93LXg6IGF1dG87XG4gICAgfVxuXG4gICAgLmNoaXAge1xuICAgICAgZmxleDogMSAwIGF1dG87XG4gICAgfVxuICB9XG59XG4iLCIvLyBDYWxlbmRhcnNcbi5jYWxlbmRhciB7XG4gIGJvcmRlcjogJGJvcmRlci13aWR0aCBzb2xpZCAkYm9yZGVyLWNvbG9yO1xuICBib3JkZXItcmFkaXVzOiAkYm9yZGVyLXJhZGl1cztcbiAgZGlzcGxheTogYmxvY2s7XG4gIG1pbi13aWR0aDogMjgwcHg7XG5cbiAgLmNhbGVuZGFyLW5hdiB7XG4gICAgYWxpZ24taXRlbXM6IGNlbnRlcjtcbiAgICBiYWNrZ3JvdW5kOiAkYmctY29sb3I7XG4gICAgYm9yZGVyLXRvcC1sZWZ0LXJhZGl1czogJGJvcmRlci1yYWRpdXM7XG4gICAgYm9yZGVyLXRvcC1yaWdodC1yYWRpdXM6ICRib3JkZXItcmFkaXVzO1xuICAgIGRpc3BsYXk6IGZsZXg7XG4gICAgZm9udC1zaXplOiAkZm9udC1zaXplLWxnO1xuICAgIHBhZGRpbmc6ICRsYXlvdXQtc3BhY2luZztcbiAgfVxuXG4gIC5jYWxlbmRhci1oZWFkZXIsXG4gIC5jYWxlbmRhci1ib2R5IHtcbiAgICBkaXNwbGF5OiBmbGV4O1xuICAgIGZsZXgtd3JhcDogd3JhcDtcbiAgICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjtcbiAgICBwYWRkaW5nOiAkbGF5b3V0LXNwYWNpbmcgMDtcblxuICAgIC5jYWxlbmRhci1kYXRlIHtcbiAgICAgIGZsZXg6IDAgMCAxNC4yOCU7IC8vIDcgY2FsZW5kYXItaXRlbXMgZWFjaCByb3dcbiAgICAgIG1heC13aWR0aDogMTQuMjglO1xuICAgIH1cbiAgfVxuXG4gIC5jYWxlbmRhci1oZWFkZXIge1xuICAgIGJhY2tncm91bmQ6ICRiZy1jb2xvcjtcbiAgICBib3JkZXItYm90dG9tOiAkYm9yZGVyLXdpZHRoIHNvbGlkICRib3JkZXItY29sb3I7XG4gICAgY29sb3I6ICRncmF5LWNvbG9yO1xuICAgIGZvbnQtc2l6ZTogJGZvbnQtc2l6ZS1zbTtcbiAgICB0ZXh0LWFsaWduOiBjZW50ZXI7XG4gIH1cblxuICAuY2FsZW5kYXItYm9keSB7XG4gICAgY29sb3I6ICRncmF5LWNvbG9yLWRhcms7XG4gIH1cblxuICAuY2FsZW5kYXItZGF0ZSB7XG4gICAgYm9yZGVyOiAwO1xuICAgIHBhZGRpbmc6ICR1bml0LTE7XG5cbiAgICAuZGF0ZS1pdGVtIHtcbiAgICAgIGFwcGVhcmFuY2U6IG5vbmU7XG4gICAgICBiYWNrZ3JvdW5kOiB0cmFuc3BhcmVudDtcbiAgICAgIGJvcmRlcjogJGJvcmRlci13aWR0aCBzb2xpZCB0cmFuc3BhcmVudDtcbiAgICAgIGJvcmRlci1yYWRpdXM6IDUwJTtcbiAgICAgIGNvbG9yOiAkZ3JheS1jb2xvci1kYXJrO1xuICAgICAgY3Vyc29yOiBwb2ludGVyO1xuICAgICAgZm9udC1zaXplOiAkZm9udC1zaXplLXNtO1xuICAgICAgaGVpZ2h0OiAkdW5pdC03O1xuICAgICAgbGluZS1oZWlnaHQ6ICR1bml0LTU7XG4gICAgICBvdXRsaW5lOiBub25lO1xuICAgICAgcGFkZGluZzogJHVuaXQtaDtcbiAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTtcbiAgICAgIHRleHQtYWxpZ246IGNlbnRlcjtcbiAgICAgIHRleHQtZGVjb3JhdGlvbjogbm9uZTtcbiAgICAgIHRyYW5zaXRpb246IGJhY2tncm91bmQgLjJzLCBib3JkZXIgLjJzLCBib3gtc2hhZG93IC4ycywgY29sb3IgLjJzO1xuICAgICAgdmVydGljYWwtYWxpZ246IG1pZGRsZTtcbiAgICAgIHdoaXRlLXNwYWNlOiBub3dyYXA7XG4gICAgICB3aWR0aDogJHVuaXQtNztcblxuICAgICAgJi5kYXRlLXRvZGF5IHtcbiAgICAgICAgYm9yZGVyLWNvbG9yOiAkc2Vjb25kYXJ5LWNvbG9yLWRhcms7XG4gICAgICAgIGNvbG9yOiAkcHJpbWFyeS1jb2xvcjtcbiAgICAgIH1cblxuICAgICAgJjpmb2N1cyB7XG4gICAgICAgIEBpbmNsdWRlIGNvbnRyb2wtc2hhZG93KCk7XG4gICAgICB9XG5cbiAgICAgICY6Zm9jdXMsXG4gICAgICAmOmhvdmVyIHtcbiAgICAgICAgYmFja2dyb3VuZDogJHNlY29uZGFyeS1jb2xvci1saWdodDtcbiAgICAgICAgYm9yZGVyLWNvbG9yOiAkc2Vjb25kYXJ5LWNvbG9yLWRhcms7XG4gICAgICAgIGNvbG9yOiAkcHJpbWFyeS1jb2xvcjtcbiAgICAgICAgdGV4dC1kZWNvcmF0aW9uOiBub25lO1xuICAgICAgfVxuICAgICAgJjphY3RpdmUsXG4gICAgICAmLmFjdGl2ZSB7XG4gICAgICAgIGJhY2tncm91bmQ6ICRwcmltYXJ5LWNvbG9yLWRhcms7XG4gICAgICAgIGJvcmRlci1jb2xvcjogZGFya2VuKCRwcmltYXJ5LWNvbG9yLWRhcmssIDUlKTtcbiAgICAgICAgY29sb3I6ICRsaWdodC1jb2xvcjtcbiAgICAgIH1cblxuICAgICAgLy8gQ2FsZW5kYXIgYmFkZ2Ugc3VwcG9ydFxuICAgICAgJi5iYWRnZSB7XG4gICAgICAgICY6OmFmdGVyIHtcbiAgICAgICAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgICAgICAgdG9wOiAzcHg7XG4gICAgICAgICAgcmlnaHQ6IDNweDtcbiAgICAgICAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSg1MCUsIC01MCUpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuXG4gICAgLmRhdGUtaXRlbSxcbiAgICAuY2FsZW5kYXItZXZlbnQge1xuICAgICAgJjpkaXNhYmxlZCxcbiAgICAgICYuZGlzYWJsZWQge1xuICAgICAgICBjdXJzb3I6IGRlZmF1bHQ7XG4gICAgICAgIG9wYWNpdHk6IC4yNTtcbiAgICAgICAgcG9pbnRlci1ldmVudHM6IG5vbmU7XG4gICAgICB9XG4gICAgfVxuXG4gICAgJi5wcmV2LW1vbnRoLFxuICAgICYubmV4dC1tb250aCB7XG4gICAgICAuZGF0ZS1pdGVtLFxuICAgICAgLmNhbGVuZGFyLWV2ZW50IHtcbiAgICAgICAgb3BhY2l0eTogLjI1O1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC5jYWxlbmRhci1yYW5nZSB7XG4gICAgcG9zaXRpb246IHJlbGF0aXZlO1xuXG4gICAgJjo6YmVmb3JlIHtcbiAgICAgIGJhY2tncm91bmQ6ICRzZWNvbmRhcnktY29sb3I7XG4gICAgICBjb250ZW50OiBcIlwiO1xuICAgICAgaGVpZ2h0OiAkdW5pdC03O1xuICAgICAgbGVmdDogMDtcbiAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICAgIHJpZ2h0OiAwO1xuICAgICAgdG9wOiA1MCU7XG4gICAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVkoLTUwJSk7XG4gICAgfVxuICAgICYucmFuZ2Utc3RhcnQge1xuICAgICAgJjo6YmVmb3JlIHtcbiAgICAgICAgbGVmdDogNTAlO1xuICAgICAgfVxuICAgIH1cbiAgICAmLnJhbmdlLWVuZCB7XG4gICAgICAmOjpiZWZvcmUge1xuICAgICAgICByaWdodDogNTAlO1xuICAgICAgfVxuICAgIH1cblxuICAgICYucmFuZ2Utc3RhcnQsXG4gICAgJi5yYW5nZS1lbmQge1xuICAgICAgLmRhdGUtaXRlbSB7XG4gICAgICAgIGJhY2tncm91bmQ6ICRwcmltYXJ5LWNvbG9yLWRhcms7XG4gICAgICAgIGJvcmRlci1jb2xvcjogZGFya2VuKCRwcmltYXJ5LWNvbG9yLWRhcmssIDUlKTtcbiAgICAgICAgY29sb3I6ICRsaWdodC1jb2xvcjtcbiAgICAgIH1cbiAgICB9XG5cbiAgICAuZGF0ZS1pdGVtIHtcbiAgICAgIGNvbG9yOiAkcHJpbWFyeS1jb2xvcjtcbiAgICB9XG4gIH1cblxuICAvLyBDYWxlbmRhcnMgc2l6ZVxuICAmLmNhbGVuZGFyLWxnIHtcbiAgICAuY2FsZW5kYXItYm9keSB7XG4gICAgICBwYWRkaW5nOiAwO1xuXG4gICAgICAuY2FsZW5kYXItZGF0ZSB7XG4gICAgICAgIGJvcmRlci1ib3R0b206ICRib3JkZXItd2lkdGggc29saWQgJGJvcmRlci1jb2xvcjtcbiAgICAgICAgYm9yZGVyLXJpZ2h0OiAkYm9yZGVyLXdpZHRoIHNvbGlkICRib3JkZXItY29sb3I7XG4gICAgICAgIGRpc3BsYXk6IGZsZXg7XG4gICAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47XG4gICAgICAgIGhlaWdodDogNS41cmVtO1xuICAgICAgICBwYWRkaW5nOiAwO1xuXG4gICAgICAgICY6bnRoLWNoaWxkKDduKSB7XG4gICAgICAgICAgYm9yZGVyLXJpZ2h0OiAwO1xuICAgICAgICB9XG4gICAgICAgICY6bnRoLWxhc3QtY2hpbGQoLW4rNykge1xuICAgICAgICAgIGJvcmRlci1ib3R0b206IDA7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICAuZGF0ZS1pdGVtIHtcbiAgICAgIGFsaWduLXNlbGY6IGZsZXgtZW5kO1xuICAgICAgaGVpZ2h0OiAkdW5pdC03O1xuICAgICAgbWFyZ2luLXJpZ2h0OiAkbGF5b3V0LXNwYWNpbmctc207XG4gICAgICBtYXJnaW4tdG9wOiAkbGF5b3V0LXNwYWNpbmctc207XG4gICAgfVxuXG4gICAgLmNhbGVuZGFyLXJhbmdlIHtcbiAgICAgICY6OmJlZm9yZSB7XG4gICAgICAgIHRvcDogMTlweDtcbiAgICAgIH1cbiAgICAgICYucmFuZ2Utc3RhcnQge1xuICAgICAgICAmOjpiZWZvcmUge1xuICAgICAgICAgIGxlZnQ6IGF1dG87XG4gICAgICAgICAgd2lkdGg6IDE5cHg7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgICYucmFuZ2UtZW5kIHtcbiAgICAgICAgJjo6YmVmb3JlIHtcbiAgICAgICAgICByaWdodDogMTlweDtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cblxuICAgIC5jYWxlbmRhci1ldmVudHMge1xuICAgICAgZmxleC1ncm93OiAxO1xuICAgICAgbGluZS1oZWlnaHQ6IDE7XG4gICAgICBvdmVyZmxvdy15OiBhdXRvO1xuICAgICAgcGFkZGluZzogJGxheW91dC1zcGFjaW5nLXNtO1xuICAgIH1cblxuICAgIC5jYWxlbmRhci1ldmVudCB7XG4gICAgICBib3JkZXItcmFkaXVzOiAkYm9yZGVyLXJhZGl1cztcbiAgICAgIGZvbnQtc2l6ZTogJGZvbnQtc2l6ZS1zbTtcbiAgICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgICAgbWFyZ2luOiAkdW5pdC1oIGF1dG87XG4gICAgICBvdmVyZmxvdzogaGlkZGVuO1xuICAgICAgcGFkZGluZzogM3B4IDRweDtcbiAgICAgIHRleHQtb3ZlcmZsb3c6IGVsbGlwc2lzO1xuICAgICAgd2hpdGUtc3BhY2U6IG5vd3JhcDtcbiAgICB9XG4gIH1cbn1cbiIsIi8vIENhcm91c2Vsc1xuLy8gVGhlIG51bWJlciBvZiBjYXJvdXNlbCBpbWFnZXNcbiRjYXJvdXNlbC1udW1iZXI6IDg7XG5cbiVjYXJvdXNlbC1pbWFnZS1jaGVja2VkIHsgXG4gIGFuaW1hdGlvbjogY2Fyb3VzZWwtc2xpZGVpbiAuNzVzIGVhc2UtaW4tb3V0IDE7XG4gIG9wYWNpdHk6IDE7XG4gIHotaW5kZXg6ICR6aW5kZXgtMTtcbn1cblxuJWNhcm91c2VsLW5hdi1jaGVja2VkIHsgXG4gIGNvbG9yOiAkZ3JheS1jb2xvci1saWdodDtcbn1cblxuLmNhcm91c2VsIHtcbiAgYmFja2dyb3VuZDogJGJnLWNvbG9yO1xuICBkaXNwbGF5OiBibG9jaztcbiAgb3ZlcmZsb3c6IGhpZGRlbjtcbiAgcG9zaXRpb246IHJlbGF0aXZlO1xuICB3aWR0aDogMTAwJTtcbiAgLXdlYmtpdC1vdmVyZmxvdy1zY3JvbGxpbmc6IHRvdWNoO1xuICB6LWluZGV4OiAkemluZGV4LTA7XG5cbiAgLmNhcm91c2VsLWNvbnRhaW5lciB7XG4gICAgaGVpZ2h0OiAxMDAlO1xuICAgIGxlZnQ6IDA7XG4gICAgcG9zaXRpb246IHJlbGF0aXZlO1xuICAgICY6OmJlZm9yZSB7XG4gICAgICBjb250ZW50OiBcIlwiO1xuICAgICAgZGlzcGxheTogYmxvY2s7XG4gICAgICBwYWRkaW5nLWJvdHRvbTogNTYuMjUlO1xuICAgIH1cblxuICAgIC5jYXJvdXNlbC1pdGVtIHtcbiAgICAgIGFuaW1hdGlvbjogY2Fyb3VzZWwtc2xpZGVvdXQgMXMgZWFzZS1pbi1vdXQgMTtcbiAgICAgIGhlaWdodDogMTAwJTtcbiAgICAgIGxlZnQ6IDA7XG4gICAgICBtYXJnaW46IDA7XG4gICAgICBvcGFjaXR5OiAwO1xuICAgICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgICAgdG9wOiAwO1xuICAgICAgd2lkdGg6IDEwMCU7XG5cbiAgICAgICY6aG92ZXIge1xuICAgICAgICAuaXRlbS1wcmV2LFxuICAgICAgICAuaXRlbS1uZXh0IHtcbiAgICAgICAgICBvcGFjaXR5OiAxO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuXG4gICAgLml0ZW0tcHJldixcbiAgICAuaXRlbS1uZXh0IHtcbiAgICAgIGJhY2tncm91bmQ6IHJnYmEoJGdyYXktY29sb3ItbGlnaHQsIC4yNSk7XG4gICAgICBib3JkZXItY29sb3I6IHJnYmEoJGdyYXktY29sb3ItbGlnaHQsIC41KTtcbiAgICAgIGNvbG9yOiAkZ3JheS1jb2xvci1saWdodDtcbiAgICAgIG9wYWNpdHk6IDA7XG4gICAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgICB0b3A6IDUwJTtcbiAgICAgIHRyYW5zaXRpb246IGFsbCAuNHM7XG4gICAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVkoLTUwJSk7XG4gICAgICB6LWluZGV4OiAkemluZGV4LTE7XG4gICAgfVxuICAgIC5pdGVtLXByZXYge1xuICAgICAgbGVmdDogMXJlbTtcbiAgICB9XG4gICAgLml0ZW0tbmV4dCB7XG4gICAgICByaWdodDogMXJlbTtcbiAgICB9XG4gIH1cblxuICAuY2Fyb3VzZWwtbG9jYXRvciB7XG4gICAgQGZvciAkaSBmcm9tIDEgdGhyb3VnaCAoJGNhcm91c2VsLW51bWJlcikge1xuICAgICAgJjpudGgtb2YtdHlwZSgjeyRpfSk6Y2hlY2tlZCB+IC5jYXJvdXNlbC1jb250YWluZXIgLmNhcm91c2VsLWl0ZW06bnRoLW9mLXR5cGUoI3skaX0pIHtcbiAgICAgICAgQGV4dGVuZCAlY2Fyb3VzZWwtaW1hZ2UtY2hlY2tlZDtcbiAgICAgIH1cbiAgICB9XG5cbiAgICBAZm9yICRpIGZyb20gMSB0aHJvdWdoICgkY2Fyb3VzZWwtbnVtYmVyKSB7XG4gICAgICAmOm50aC1vZi10eXBlKCN7JGl9KTpjaGVja2VkIH4gLmNhcm91c2VsLW5hdiAubmF2LWl0ZW06bnRoLW9mLXR5cGUoI3skaX0pIHtcbiAgICAgICAgQGV4dGVuZCAlY2Fyb3VzZWwtbmF2LWNoZWNrZWQ7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLmNhcm91c2VsLW5hdiB7XG4gICAgYm90dG9tOiAkbGF5b3V0LXNwYWNpbmc7XG4gICAgZGlzcGxheTogZmxleDtcbiAgICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjtcbiAgICBsZWZ0OiA1MCU7XG4gICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWCgtNTAlKTtcbiAgICB3aWR0aDogMTByZW07XG4gICAgei1pbmRleDogJHppbmRleC0xO1xuXG4gICAgLm5hdi1pdGVtIHtcbiAgICAgIGNvbG9yOiByZ2JhKCRncmF5LWNvbG9yLWxpZ2h0LCAuNSk7XG4gICAgICBkaXNwbGF5OiBibG9jaztcbiAgICAgIGZsZXg6IDEgMCBhdXRvO1xuICAgICAgaGVpZ2h0OiAkdW5pdC04O1xuICAgICAgbWFyZ2luOiAkdW5pdC0xO1xuICAgICAgbWF4LXdpZHRoOiAyLjVyZW07XG4gICAgICBwb3NpdGlvbjogcmVsYXRpdmU7XG5cbiAgICAgICY6OmJlZm9yZSB7XG4gICAgICAgIGJhY2tncm91bmQ6IGN1cnJlbnRDb2xvcjtcbiAgICAgICAgY29udGVudDogXCJcIjtcbiAgICAgICAgZGlzcGxheTogYmxvY2s7XG4gICAgICAgIGhlaWdodDogJHVuaXQtaDtcbiAgICAgICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgICAgICB0b3A6IC41cmVtO1xuICAgICAgICB3aWR0aDogMTAwJTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cblxuQGtleWZyYW1lcyBjYXJvdXNlbC1zbGlkZWluIHtcbiAgMCUge1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWCgxMDAlKTtcbiAgfVxuICAxMDAlIHtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVgoMCk7XG4gIH1cbn1cblxuQGtleWZyYW1lcyBjYXJvdXNlbC1zbGlkZW91dCB7XG4gIDAlIHtcbiAgICBvcGFjaXR5OiAxO1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWCgwKTtcbiAgfVxuICAxMDAlIHtcbiAgICBvcGFjaXR5OiAxO1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWCgtNTAlKTtcbiAgfVxufVxuIiwiLy8gSW1hZ2UgY29tcGFyaXNvbiBzbGlkZXJcbi8vIENyZWRpdDogaHR0cDovL2NvZGVwZW4uaW8vc29saXBzaXN0YWNwL3Blbi9HcG1hcVxuLmNvbXBhcmlzb24tc2xpZGVyIHtcbiAgaGVpZ2h0OiA1MHZoO1xuICBvdmVyZmxvdzogaGlkZGVuO1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gIHdpZHRoOiAxMDAlO1xuICAtd2Via2l0LW92ZXJmbG93LXNjcm9sbGluZzogdG91Y2g7XG5cbiAgLmNvbXBhcmlzb24tYmVmb3JlLFxuICAuY29tcGFyaXNvbi1hZnRlciB7XG4gICAgaGVpZ2h0OiAxMDAlO1xuICAgIGxlZnQ6IDA7XG4gICAgbWFyZ2luOiAwO1xuICAgIG92ZXJmbG93OiBoaWRkZW47XG4gICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgIHRvcDogMDtcblxuICAgIGltZyB7XG4gICAgICBoZWlnaHQ6IDEwMCU7XG4gICAgICBvYmplY3QtZml0OiBjb3ZlcjtcbiAgICAgIG9iamVjdC1wb3NpdGlvbjogbGVmdCBjZW50ZXI7XG4gICAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgICB3aWR0aDogMTAwJTtcbiAgICB9XG4gIH1cblxuICAuY29tcGFyaXNvbi1iZWZvcmUge1xuICAgIHdpZHRoOiAxMDAlO1xuICAgIHotaW5kZXg6IDE7XG5cbiAgICAuY29tcGFyaXNvbi1sYWJlbCB7XG4gICAgICByaWdodDogJHVuaXQtNDtcbiAgICB9XG4gIH1cblxuICAuY29tcGFyaXNvbi1hZnRlciB7XG4gICAgbWF4LXdpZHRoOiAxMDAlO1xuICAgIG1pbi13aWR0aDogMDtcbiAgICB6LWluZGV4OiAyO1xuXG4gICAgJjo6YmVmb3JlIHtcbiAgICAgIGJhY2tncm91bmQ6IHRyYW5zcGFyZW50O1xuICAgICAgY29udGVudDogXCJcIjtcbiAgICAgIGN1cnNvcjogZGVmYXVsdDtcbiAgICAgIGhlaWdodDogMTAwJTtcbiAgICAgIGxlZnQ6IDA7XG4gICAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgICByaWdodDogJHVuaXQtNDtcbiAgICAgIHRvcDogMDtcbiAgICAgIHotaW5kZXg6ICR6aW5kZXgtMDtcbiAgICB9XG5cbiAgICAmOjphZnRlciB7XG4gICAgICBiYWNrZ3JvdW5kOiBjdXJyZW50Q29sb3I7XG4gICAgICBib3JkZXItcmFkaXVzOiA1MCU7XG4gICAgICBib3gtc2hhZG93OiAwIC01cHgsIDAgNXB4O1xuICAgICAgY29sb3I6ICRsaWdodC1jb2xvcjtcbiAgICAgIGNvbnRlbnQ6IFwiXCI7XG4gICAgICBoZWlnaHQ6IDNweDtcbiAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICAgIHJpZ2h0OiAkdW5pdC0yO1xuICAgICAgdG9wOiA1MCU7XG4gICAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSg1MCUsIC01MCUpO1xuICAgICAgd2lkdGg6IDNweDtcbiAgICB9XG5cbiAgICAuY29tcGFyaXNvbi1sYWJlbCB7XG4gICAgICBsZWZ0OiAkdW5pdC00O1xuICAgIH1cbiAgfVxuXG4gIC5jb21wYXJpc29uLXJlc2l6ZXIge1xuICAgIGFuaW1hdGlvbjogZmlyc3QtcnVuIDEuNXMgMSBlYXNlLWluLW91dDtcbiAgICBjdXJzb3I6IGV3LXJlc2l6ZTtcbiAgICBoZWlnaHQ6ICR1bml0LTQ7XG4gICAgbGVmdDogMDtcbiAgICBtYXgtd2lkdGg6IDEwMCU7XG4gICAgbWluLXdpZHRoOiAkdW5pdC00O1xuICAgIG9wYWNpdHk6IDA7XG4gICAgb3V0bGluZTogbm9uZTtcbiAgICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gICAgcmVzaXplOiBob3Jpem9udGFsO1xuICAgIHRvcDogNTAlO1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWSgtNTAlKSBzY2FsZVkoMzApO1xuICAgIHdpZHRoOiAwO1xuICB9XG5cbiAgLmNvbXBhcmlzb24tbGFiZWwge1xuICAgIGJhY2tncm91bmQ6IHJnYmEoJGRhcmstY29sb3IsIC41KTtcbiAgICBib3R0b206ICR1bml0LTQ7XG4gICAgY29sb3I6ICRsaWdodC1jb2xvcjtcbiAgICBwYWRkaW5nOiAkdW5pdC0xICR1bml0LTI7XG4gICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgIHVzZXItc2VsZWN0OiBub25lO1xuICB9XG59XG5cbkBrZXlmcmFtZXMgZmlyc3QtcnVuIHtcbiAgMCUge1xuICAgIHdpZHRoOiAwO1xuICB9XG4gIDI1JSB7XG4gICAgd2lkdGg6ICR1bml0LTEyO1xuICB9XG4gIDUwJSB7XG4gICAgd2lkdGg6ICR1bml0LTQ7XG4gIH1cbiAgNzUlIHtcbiAgICB3aWR0aDogJHVuaXQtNjtcbiAgfVxuICAxMDAlIHtcbiAgICB3aWR0aDogMDtcbiAgfVxufVxuIiwiLy8gRmlsdGVycyBcbi8vIFRoZSBudW1iZXIgb2YgZmlsdGVyIG9wdGlvbnMgXG4kZmlsdGVyLW51bWJlcjogOCAhZGVmYXVsdDtcblxuJWZpbHRlci1jaGVja2VkLW5hdiB7IFxuICBiYWNrZ3JvdW5kOiAkcHJpbWFyeS1jb2xvcjtcbiAgY29sb3I6ICRsaWdodC1jb2xvcjtcbn1cblxuJWZpbHRlci1jaGVja2VkLWJvZHkgeyBcbiAgZGlzcGxheTogbm9uZTtcbn1cblxuLmZpbHRlciB7XG4gIC5maWx0ZXItbmF2IHtcbiAgICBtYXJnaW46ICRsYXlvdXQtc3BhY2luZyAwO1xuICB9XG5cbiAgLmZpbHRlci1ib2R5IHtcbiAgICBkaXNwbGF5OiBmbGV4O1xuICAgIGZsZXgtd3JhcDogd3JhcDtcbiAgfVxuXG4gIC5maWx0ZXItdGFnIHtcbiAgICBAZm9yICRpIGZyb20gMCB0aHJvdWdoICgkZmlsdGVyLW51bWJlcikge1xuICAgICAgJiN0YWctI3skaX06Y2hlY2tlZCB+IC5maWx0ZXItbmF2IC5jaGlwW2Zvcj1cInRhZy0jeyRpfVwiXSB7XG4gICAgICAgIEBleHRlbmQgJWZpbHRlci1jaGVja2VkLW5hdjtcbiAgICAgIH1cbiAgICB9XG5cbiAgICBAZm9yICRpIGZyb20gMSB0aHJvdWdoICgkZmlsdGVyLW51bWJlcikge1xuICAgICAgJiN0YWctI3skaX06Y2hlY2tlZCB+IC5maWx0ZXItYm9keSAuZmlsdGVyLWl0ZW06bm90KFtkYXRhLXRhZ349XCJ0YWctI3skaX1cIl0pIHtcbiAgICAgICAgQGV4dGVuZCAlZmlsdGVyLWNoZWNrZWQtYm9keTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cbiIsIi8vIE1ldGVyc1xuLy8gQ3JlZGl0OiBodHRwczovL2Nzcy10cmlja3MuY29tL2h0bWw1LW1ldGVyLWVsZW1lbnQvXG4ubWV0ZXIge1xuICBhcHBlYXJhbmNlOiBub25lO1xuICBiYWNrZ3JvdW5kOiAkYmctY29sb3I7XG4gIGJvcmRlcjogMDtcbiAgYm9yZGVyLXJhZGl1czogJGJvcmRlci1yYWRpdXM7XG4gIGRpc3BsYXk6IGJsb2NrO1xuICB3aWR0aDogMTAwJTtcbiAgaGVpZ2h0OiAkdW5pdC00O1xuXG4gICY6Oi13ZWJraXQtbWV0ZXItaW5uZXItZWxlbWVudCB7XG4gICAgZGlzcGxheTogYmxvY2s7XG4gIH1cblxuICAmOjotd2Via2l0LW1ldGVyLWJhcixcbiAgJjo6LXdlYmtpdC1tZXRlci1vcHRpbXVtLXZhbHVlLFxuICAmOjotd2Via2l0LW1ldGVyLXN1Ym9wdGltdW0tdmFsdWUsXG4gICY6Oi13ZWJraXQtbWV0ZXItZXZlbi1sZXNzLWdvb2QtdmFsdWUge1xuICAgIGJvcmRlci1yYWRpdXM6ICRib3JkZXItcmFkaXVzO1xuICB9XG5cbiAgJjo6LXdlYmtpdC1tZXRlci1iYXIge1xuICAgIGJhY2tncm91bmQ6ICRiZy1jb2xvcjtcbiAgfVxuXG4gICY6Oi13ZWJraXQtbWV0ZXItb3B0aW11bS12YWx1ZSB7XG4gICAgYmFja2dyb3VuZDogJHN1Y2Nlc3MtY29sb3I7XG4gIH1cblxuICAmOjotd2Via2l0LW1ldGVyLXN1Ym9wdGltdW0tdmFsdWUge1xuICAgIGJhY2tncm91bmQ6ICR3YXJuaW5nLWNvbG9yO1xuICB9XG5cbiAgJjo6LXdlYmtpdC1tZXRlci1ldmVuLWxlc3MtZ29vZC12YWx1ZSB7XG4gICAgYmFja2dyb3VuZDogJGVycm9yLWNvbG9yO1xuICB9XG5cbiAgJjo6LW1vei1tZXRlci1iYXIsXG4gICY6LW1vei1tZXRlci1vcHRpbXVtLFxuICAmOi1tb3otbWV0ZXItc3ViLW9wdGltdW0sXG4gICY6LW1vei1tZXRlci1zdWItc3ViLW9wdGltdW0ge1xuICAgIGJvcmRlci1yYWRpdXM6ICRib3JkZXItcmFkaXVzO1xuICB9XG5cbiAgJjotbW96LW1ldGVyLW9wdGltdW06Oi1tb3otbWV0ZXItYmFyIHtcbiAgICBiYWNrZ3JvdW5kOiAkc3VjY2Vzcy1jb2xvcjtcbiAgfVxuXG4gICY6LW1vei1tZXRlci1zdWItb3B0aW11bTo6LW1vei1tZXRlci1iYXIge1xuICAgIGJhY2tncm91bmQ6ICR3YXJuaW5nLWNvbG9yO1xuICB9XG5cbiAgJjotbW96LW1ldGVyLXN1Yi1zdWItb3B0aW11bTo6LW1vei1tZXRlci1iYXIge1xuICAgIGJhY2tncm91bmQ6ICRlcnJvci1jb2xvcjtcbiAgfVxufVxuIiwiLy8gT2ZmIGNhbnZhcyBtZW51c1xuJG9mZi1jYW52YXMtYnJlYWtwb2ludDogJHNpemUtbGcgIWRlZmF1bHQ7XG5cbi5vZmYtY2FudmFzIHtcbiAgZGlzcGxheTogZmxleDtcbiAgZmxleC1mbG93OiBub3dyYXA7XG4gIGhlaWdodDogMTAwJTtcbiAgcG9zaXRpb246IHJlbGF0aXZlO1xuICB3aWR0aDogMTAwJTtcblxuICAub2ZmLWNhbnZhcy10b2dnbGUge1xuICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICB0b3A6ICRsYXlvdXQtc3BhY2luZztcbiAgICB0cmFuc2l0aW9uOiBub25lO1xuICAgIHotaW5kZXg6ICR6aW5kZXgtMDtcbiAgICBAaWYgJHJ0bCA9PSB0cnVlIHtcbiAgICAgIHJpZ2h0OiAkbGF5b3V0LXNwYWNpbmc7XG4gICAgfSBAZWxzZSB7XG4gICAgICBsZWZ0OiAkbGF5b3V0LXNwYWNpbmc7XG4gICAgfVxuICB9XG5cbiAgLm9mZi1jYW52YXMtc2lkZWJhciB7XG4gICAgYmFja2dyb3VuZDogJGJnLWNvbG9yO1xuICAgIGJvdHRvbTogMDtcbiAgICBtaW4td2lkdGg6IDEwcmVtO1xuICAgIG92ZXJmbG93LXk6IGF1dG87XG4gICAgcG9zaXRpb246IGZpeGVkO1xuICAgIHRvcDogMDtcbiAgICB0cmFuc2l0aW9uOiB0cmFuc2Zvcm0gLjI1cztcbiAgICB6LWluZGV4OiAkemluZGV4LTI7XG4gICAgQGlmICRydGwgPT0gdHJ1ZSB7XG4gICAgICByaWdodDogMDtcbiAgICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWCgxMDAlKTtcbiAgICB9IEBlbHNlIHtcbiAgICAgIGxlZnQ6IDA7XG4gICAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVgoLTEwMCUpO1xuICAgIH1cbiAgfVxuXG4gIC5vZmYtY2FudmFzLWNvbnRlbnQge1xuICAgIGZsZXg6IDEgMSBhdXRvO1xuICAgIGhlaWdodDogMTAwJTtcbiAgICBwYWRkaW5nOiAkbGF5b3V0LXNwYWNpbmcgJGxheW91dC1zcGFjaW5nICRsYXlvdXQtc3BhY2luZyA0cmVtO1xuICB9XG5cbiAgLm9mZi1jYW52YXMtb3ZlcmxheSB7XG4gICAgYmFja2dyb3VuZDogcmdiYSgkZGFyay1jb2xvciwgLjEpO1xuICAgIGJvcmRlci1jb2xvcjogdHJhbnNwYXJlbnQ7XG4gICAgYm9yZGVyLXJhZGl1czogMDtcbiAgICBib3R0b206IDA7XG4gICAgZGlzcGxheTogbm9uZTtcbiAgICBoZWlnaHQ6IDEwMCU7XG4gICAgbGVmdDogMDtcbiAgICBwb3NpdGlvbjogZml4ZWQ7XG4gICAgcmlnaHQ6IDA7XG4gICAgdG9wOiAwO1xuICAgIHdpZHRoOiAxMDAlO1xuICB9XG5cbiAgLm9mZi1jYW52YXMtc2lkZWJhciB7XG4gICAgJjp0YXJnZXQsXG4gICAgJi5hY3RpdmUge1xuICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGVYKDApO1xuICAgIH1cblxuICAgICY6dGFyZ2V0IH4gLm9mZi1jYW52YXMtb3ZlcmxheSxcbiAgICAmLmFjdGl2ZSB+IC5vZmYtY2FudmFzLW92ZXJsYXkge1xuICAgICAgZGlzcGxheTogYmxvY2s7XG4gICAgICB6LWluZGV4OiAkemluZGV4LTE7XG4gICAgfVxuICB9XG59XG5cbi8vIFJlc3BvbnNpdmUgbGF5b3V0XG5AbWVkaWEgKG1pbi13aWR0aDogJG9mZi1jYW52YXMtYnJlYWtwb2ludCkge1xuICAub2ZmLWNhbnZhcyB7XG4gICAgJi5vZmYtY2FudmFzLXNpZGViYXItc2hvdyB7XG4gICAgICAub2ZmLWNhbnZhcy10b2dnbGUge1xuICAgICAgICBkaXNwbGF5OiBub25lO1xuICAgICAgfVxuICBcbiAgICAgIC5vZmYtY2FudmFzLXNpZGViYXIge1xuICAgICAgICBmbGV4OiAwIDAgYXV0bztcbiAgICAgICAgcG9zaXRpb246IHJlbGF0aXZlO1xuICAgICAgICB0cmFuc2Zvcm06IG5vbmU7XG4gICAgICB9XG5cbiAgICAgIC5vZmYtY2FudmFzLW92ZXJsYXkge1xuICAgICAgICBkaXNwbGF5OiBub25lICFpbXBvcnRhbnQ7XG4gICAgICB9XG4gICAgfVxuICB9XG59XG4iLCIvLyBQYXJhbGxheFxuJHBhcmFsbGF4LWRlZzogM2RlZyAhZGVmYXVsdDtcbiRwYXJhbGxheC1vZmZzZXQ6IDQuNXB4ICFkZWZhdWx0O1xuJHBhcmFsbGF4LW9mZnNldC16OiA1MHB4ICFkZWZhdWx0O1xuJHBhcmFsbGF4LXBlcnNwZWN0aXZlOiAxMDAwcHggIWRlZmF1bHQ7XG4kcGFyYWxsYXgtc2NhbGU6IC45NSAhZGVmYXVsdDtcbiRwYXJhbGxheC1mYWRlLWNvbG9yOiByZ2JhKDI1NSwgMjU1LCAyNTUsIC4zNSkgIWRlZmF1bHQ7XG5cbi8vIE1peGluOiBQYXJhbGxheCBkaXJlY3Rpb25cbkBtaXhpbiBwYXJhbGxheC1kaXIoKSB7XG4gIGhlaWdodDogNTAlO1xuICBvdXRsaW5lOiBub25lO1xuICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gIHdpZHRoOiA1MCU7XG4gIHotaW5kZXg6ICR6aW5kZXgtMTtcbn1cblxuLnBhcmFsbGF4IHtcbiAgZGlzcGxheTogYmxvY2s7XG4gIGhlaWdodDogYXV0bztcbiAgcG9zaXRpb246IHJlbGF0aXZlO1xuICB3aWR0aDogYXV0bztcblxuICAucGFyYWxsYXgtY29udGVudCB7XG4gICAgQGluY2x1ZGUgc2hhZG93LXZhcmlhbnQoMXJlbSk7XG4gICAgaGVpZ2h0OiBhdXRvO1xuICAgIHRyYW5zZm9ybTogcGVyc3BlY3RpdmUoJHBhcmFsbGF4LXBlcnNwZWN0aXZlKTtcbiAgICB0cmFuc2Zvcm0tc3R5bGU6IHByZXNlcnZlLTNkO1xuICAgIHRyYW5zaXRpb246IGFsbCAuNHMgZWFzZTtcbiAgICB3aWR0aDogMTAwJTtcblxuICAgICY6OmJlZm9yZSB7XG4gICAgICBjb250ZW50OiBcIlwiO1xuICAgICAgZGlzcGxheTogYmxvY2s7XG4gICAgICBoZWlnaHQ6IDEwMCU7XG4gICAgICBsZWZ0OiAwO1xuICAgICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgICAgdG9wOiAwO1xuICAgICAgd2lkdGg6IDEwMCU7XG4gICAgfVxuICB9XG5cbiAgLnBhcmFsbGF4LWZyb250IHtcbiAgICBhbGlnbi1pdGVtczogY2VudGVyO1xuICAgIGNvbG9yOiAkbGlnaHQtY29sb3I7XG4gICAgZGlzcGxheTogZmxleDtcbiAgICBoZWlnaHQ6IDEwMCU7XG4gICAganVzdGlmeS1jb250ZW50OiBjZW50ZXI7XG4gICAgbGVmdDogMDtcbiAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgdGV4dC1hbGlnbjogY2VudGVyO1xuICAgIHRleHQtc2hhZG93OiAwIDAgMjBweCByZ2JhKCRkYXJrLWNvbG9yLCAuNzUpO1xuICAgIHRvcDogMDtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVooJHBhcmFsbGF4LW9mZnNldC16KSBzY2FsZSgkcGFyYWxsYXgtc2NhbGUpO1xuICAgIHRyYW5zaXRpb246IHRyYW5zZm9ybSAuNHM7XG4gICAgd2lkdGg6IDEwMCU7XG4gICAgei1pbmRleDogJHppbmRleC0wO1xuICB9XG5cbiAgLnBhcmFsbGF4LXRvcC1sZWZ0IHtcbiAgICBAaW5jbHVkZSBwYXJhbGxheC1kaXIoKTtcbiAgICBsZWZ0OiAwO1xuICAgIHRvcDogMDtcblxuICAgICY6Zm9jdXMgfiAucGFyYWxsYXgtY29udGVudCxcbiAgICAmOmhvdmVyIH4gLnBhcmFsbGF4LWNvbnRlbnQge1xuICAgICAgdHJhbnNmb3JtOiBwZXJzcGVjdGl2ZSgkcGFyYWxsYXgtcGVyc3BlY3RpdmUpIHJvdGF0ZVgoJHBhcmFsbGF4LWRlZykgcm90YXRlWSgtJHBhcmFsbGF4LWRlZyk7XG5cbiAgICAgICY6OmJlZm9yZSB7XG4gICAgICAgIGJhY2tncm91bmQ6IGxpbmVhci1ncmFkaWVudCgxMzVkZWcsICRwYXJhbGxheC1mYWRlLWNvbG9yIDAlLCB0cmFuc3BhcmVudCA1MCUpO1xuICAgICAgfVxuXG4gICAgICAucGFyYWxsYXgtZnJvbnQge1xuICAgICAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZTNkKCRwYXJhbGxheC1vZmZzZXQsICRwYXJhbGxheC1vZmZzZXQsICRwYXJhbGxheC1vZmZzZXQteikgc2NhbGUoJHBhcmFsbGF4LXNjYWxlKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICAucGFyYWxsYXgtdG9wLXJpZ2h0IHtcbiAgICBAaW5jbHVkZSBwYXJhbGxheC1kaXIoKTtcbiAgICByaWdodDogMDtcbiAgICB0b3A6IDA7XG5cbiAgICAmOmZvY3VzIH4gLnBhcmFsbGF4LWNvbnRlbnQsXG4gICAgJjpob3ZlciB+IC5wYXJhbGxheC1jb250ZW50IHtcbiAgICAgIHRyYW5zZm9ybTogcGVyc3BlY3RpdmUoJHBhcmFsbGF4LXBlcnNwZWN0aXZlKSByb3RhdGVYKCRwYXJhbGxheC1kZWcpIHJvdGF0ZVkoJHBhcmFsbGF4LWRlZyk7XG5cbiAgICAgICY6OmJlZm9yZSB7XG4gICAgICAgIGJhY2tncm91bmQ6IGxpbmVhci1ncmFkaWVudCgtMTM1ZGVnLCAkcGFyYWxsYXgtZmFkZS1jb2xvciAwJSwgdHJhbnNwYXJlbnQgNTAlKTtcbiAgICAgIH1cblxuICAgICAgLnBhcmFsbGF4LWZyb250IHtcbiAgICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUzZCgtJHBhcmFsbGF4LW9mZnNldCwgJHBhcmFsbGF4LW9mZnNldCwgJHBhcmFsbGF4LW9mZnNldC16KSBzY2FsZSgkcGFyYWxsYXgtc2NhbGUpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC5wYXJhbGxheC1ib3R0b20tbGVmdCB7XG4gICAgQGluY2x1ZGUgcGFyYWxsYXgtZGlyKCk7XG4gICAgYm90dG9tOiAwO1xuICAgIGxlZnQ6IDA7XG5cbiAgICAmOmZvY3VzIH4gLnBhcmFsbGF4LWNvbnRlbnQsXG4gICAgJjpob3ZlciB+IC5wYXJhbGxheC1jb250ZW50IHtcbiAgICAgIHRyYW5zZm9ybTogcGVyc3BlY3RpdmUoJHBhcmFsbGF4LXBlcnNwZWN0aXZlKSByb3RhdGVYKC0kcGFyYWxsYXgtZGVnKSByb3RhdGVZKC0kcGFyYWxsYXgtZGVnKTtcblxuICAgICAgJjo6YmVmb3JlIHtcbiAgICAgICAgYmFja2dyb3VuZDogbGluZWFyLWdyYWRpZW50KDQ1ZGVnLCAkcGFyYWxsYXgtZmFkZS1jb2xvciAwJSwgdHJhbnNwYXJlbnQgNTAlKTtcbiAgICAgIH1cblxuICAgICAgLnBhcmFsbGF4LWZyb250IHtcbiAgICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUzZCgkcGFyYWxsYXgtb2Zmc2V0LCAtJHBhcmFsbGF4LW9mZnNldCwgJHBhcmFsbGF4LW9mZnNldC16KSBzY2FsZSgkcGFyYWxsYXgtc2NhbGUpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC5wYXJhbGxheC1ib3R0b20tcmlnaHQge1xuICAgIEBpbmNsdWRlIHBhcmFsbGF4LWRpcigpO1xuICAgIGJvdHRvbTogMDtcbiAgICByaWdodDogMDtcblxuICAgICY6Zm9jdXMgfiAucGFyYWxsYXgtY29udGVudCxcbiAgICAmOmhvdmVyIH4gLnBhcmFsbGF4LWNvbnRlbnQge1xuICAgICAgdHJhbnNmb3JtOiBwZXJzcGVjdGl2ZSgkcGFyYWxsYXgtcGVyc3BlY3RpdmUpIHJvdGF0ZVgoLSRwYXJhbGxheC1kZWcpIHJvdGF0ZVkoJHBhcmFsbGF4LWRlZyk7XG5cbiAgICAgICY6OmJlZm9yZSB7XG4gICAgICAgIGJhY2tncm91bmQ6IGxpbmVhci1ncmFkaWVudCgtNDVkZWcsICRwYXJhbGxheC1mYWRlLWNvbG9yIDAlLCB0cmFuc3BhcmVudCA1MCUpO1xuICAgICAgfVxuXG4gICAgICAucGFyYWxsYXgtZnJvbnQge1xuICAgICAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZTNkKC0kcGFyYWxsYXgtb2Zmc2V0LCAtJHBhcmFsbGF4LW9mZnNldCwgJHBhcmFsbGF4LW9mZnNldC16KSBzY2FsZSgkcGFyYWxsYXgtc2NhbGUpO1xuICAgICAgfVxuICAgIH1cbiAgfVxufVxuIiwiLy8gUHJvZ3Jlc3Ncbi8vIENyZWRpdDogaHR0cHM6Ly9jc3MtdHJpY2tzLmNvbS9odG1sNS1wcm9ncmVzcy1lbGVtZW50L1xuLnByb2dyZXNzIHtcbiAgYXBwZWFyYW5jZTogbm9uZTtcbiAgYmFja2dyb3VuZDogJGJnLWNvbG9yLWRhcms7XG4gIGJvcmRlcjogMDtcbiAgYm9yZGVyLXJhZGl1czogJGJvcmRlci1yYWRpdXM7XG4gIGNvbG9yOiAkcHJpbWFyeS1jb2xvcjtcbiAgaGVpZ2h0OiAkdW5pdC0xO1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gIHdpZHRoOiAxMDAlO1xuXG4gICY6Oi13ZWJraXQtcHJvZ3Jlc3MtYmFyIHtcbiAgICBiYWNrZ3JvdW5kOiB0cmFuc3BhcmVudDtcbiAgICBib3JkZXItcmFkaXVzOiAkYm9yZGVyLXJhZGl1cztcbiAgfVxuXG4gICY6Oi13ZWJraXQtcHJvZ3Jlc3MtdmFsdWUge1xuICAgIGJhY2tncm91bmQ6ICRwcmltYXJ5LWNvbG9yO1xuICAgIGJvcmRlci1yYWRpdXM6ICRib3JkZXItcmFkaXVzO1xuICB9XG5cbiAgJjo6LW1vei1wcm9ncmVzcy1iYXIge1xuICAgIGJhY2tncm91bmQ6ICRwcmltYXJ5LWNvbG9yO1xuICAgIGJvcmRlci1yYWRpdXM6ICRib3JkZXItcmFkaXVzO1xuICB9XG5cbiAgJjppbmRldGVybWluYXRlIHtcbiAgICBhbmltYXRpb246IHByb2dyZXNzLWluZGV0ZXJtaW5hdGUgMS41cyBsaW5lYXIgaW5maW5pdGU7XG4gICAgYmFja2dyb3VuZDogJGJnLWNvbG9yLWRhcmsgbGluZWFyLWdyYWRpZW50KHRvIHJpZ2h0LCAkcHJpbWFyeS1jb2xvciAzMCUsICRiZy1jb2xvci1kYXJrIDMwJSkgdG9wIGxlZnQgLyAxNTAlIDE1MCUgbm8tcmVwZWF0O1xuXG4gICAgJjo6LW1vei1wcm9ncmVzcy1iYXIge1xuICAgICAgYmFja2dyb3VuZDogdHJhbnNwYXJlbnQ7XG4gICAgfVxuICB9XG59XG5cbkBrZXlmcmFtZXMgcHJvZ3Jlc3MtaW5kZXRlcm1pbmF0ZSB7XG4gIDAlIHtcbiAgICBiYWNrZ3JvdW5kLXBvc2l0aW9uOiAyMDAlIDA7XG4gIH1cbiAgMTAwJSB7XG4gICAgYmFja2dyb3VuZC1wb3NpdGlvbjogLTIwMCUgMDtcbiAgfVxufVxuIiwiLy8gU2xpZGVyc1xuLy8gQ3JlZGl0OiBodHRwczovL2Nzcy10cmlja3MuY29tL3N0eWxpbmctY3Jvc3MtYnJvd3Nlci1jb21wYXRpYmxlLXJhbmdlLWlucHV0cy1jc3MvXG4uc2xpZGVyIHtcbiAgYXBwZWFyYW5jZTogbm9uZTtcbiAgYmFja2dyb3VuZDogdHJhbnNwYXJlbnQ7XG4gIGRpc3BsYXk6IGJsb2NrO1xuICB3aWR0aDogMTAwJTtcbiAgaGVpZ2h0OiAkdW5pdC02O1xuXG4gICY6Zm9jdXMge1xuICAgIEBpbmNsdWRlIGNvbnRyb2wtc2hhZG93KCk7XG4gICAgb3V0bGluZTogbm9uZTtcbiAgfVxuXG4gICYudG9vbHRpcDpub3QoW2RhdGEtdG9vbHRpcF0pIHtcbiAgICAmOjphZnRlciB7XG4gICAgICBjb250ZW50OiBhdHRyKHZhbHVlKTtcbiAgICB9XG4gIH1cblxuICAvLyBTbGlkZXIgVGh1bWJcbiAgJjo6LXdlYmtpdC1zbGlkZXItdGh1bWIge1xuICAgIC13ZWJraXQtYXBwZWFyYW5jZTogbm9uZTtcbiAgICBiYWNrZ3JvdW5kOiAkcHJpbWFyeS1jb2xvcjtcbiAgICBib3JkZXI6IDA7XG4gICAgYm9yZGVyLXJhZGl1czogNTAlO1xuICAgIGhlaWdodDogJHVuaXQtMztcbiAgICBtYXJnaW4tdG9wOiAtKCR1bml0LTMgLSAkdW5pdC1oKSAvIDI7XG4gICAgdHJhbnNpdGlvbjogdHJhbnNmb3JtIC4ycztcbiAgICB3aWR0aDogJHVuaXQtMztcbiAgfVxuICAmOjotbW96LXJhbmdlLXRodW1iIHtcbiAgICBiYWNrZ3JvdW5kOiAkcHJpbWFyeS1jb2xvcjtcbiAgICBib3JkZXI6IDA7XG4gICAgYm9yZGVyLXJhZGl1czogNTAlO1xuICAgIGhlaWdodDogJHVuaXQtMztcbiAgICB0cmFuc2l0aW9uOiB0cmFuc2Zvcm0gLjJzO1xuICAgIHdpZHRoOiAkdW5pdC0zO1xuICB9XG4gICY6Oi1tcy10aHVtYiB7XG4gICAgYmFja2dyb3VuZDogJHByaW1hcnktY29sb3I7XG4gICAgYm9yZGVyOiAwO1xuICAgIGJvcmRlci1yYWRpdXM6IDUwJTtcbiAgICBoZWlnaHQ6ICR1bml0LTM7XG4gICAgdHJhbnNpdGlvbjogdHJhbnNmb3JtIC4ycztcbiAgICB3aWR0aDogJHVuaXQtMztcbiAgfVxuXG4gICY6YWN0aXZlIHtcbiAgICAmOjotd2Via2l0LXNsaWRlci10aHVtYiB7XG4gICAgICB0cmFuc2Zvcm06IHNjYWxlKDEuMjUpO1xuICAgIH1cbiAgICAmOjotbW96LXJhbmdlLXRodW1iIHtcbiAgICAgIHRyYW5zZm9ybTogc2NhbGUoMS4yNSk7XG4gICAgfVxuICAgICY6Oi1tcy10aHVtYiB7XG4gICAgICB0cmFuc2Zvcm06IHNjYWxlKDEuMjUpO1xuICAgIH1cbiAgfVxuXG4gICY6ZGlzYWJsZWQsXG4gICYuZGlzYWJsZWQge1xuICAgICY6Oi13ZWJraXQtc2xpZGVyLXRodW1iIHtcbiAgICAgIGJhY2tncm91bmQ6ICRncmF5LWNvbG9yLWxpZ2h0O1xuICAgICAgdHJhbnNmb3JtOiBzY2FsZSgxKTtcbiAgICB9XG4gICAgJjo6LW1vei1yYW5nZS10aHVtYiB7XG4gICAgICBiYWNrZ3JvdW5kOiAkZ3JheS1jb2xvci1saWdodDtcbiAgICAgIHRyYW5zZm9ybTogc2NhbGUoMSk7XG4gICAgfVxuICAgICY6Oi1tcy10aHVtYiB7XG4gICAgICBiYWNrZ3JvdW5kOiAkZ3JheS1jb2xvci1saWdodDtcbiAgICAgIHRyYW5zZm9ybTogc2NhbGUoMSk7XG4gICAgfVxuICB9XG5cbiAgLy8gU2xpZGVyIFRyYWNrXG4gICY6Oi13ZWJraXQtc2xpZGVyLXJ1bm5hYmxlLXRyYWNrIHtcbiAgICBiYWNrZ3JvdW5kOiAkYmctY29sb3ItZGFyaztcbiAgICBib3JkZXItcmFkaXVzOiAkYm9yZGVyLXJhZGl1cztcbiAgICBoZWlnaHQ6ICR1bml0LWg7XG4gICAgd2lkdGg6IDEwMCU7XG4gIH1cbiAgJjo6LW1vei1yYW5nZS10cmFjayB7XG4gICAgYmFja2dyb3VuZDogJGJnLWNvbG9yLWRhcms7XG4gICAgYm9yZGVyLXJhZGl1czogJGJvcmRlci1yYWRpdXM7XG4gICAgaGVpZ2h0OiAkdW5pdC1oO1xuICAgIHdpZHRoOiAxMDAlO1xuICB9XG4gICY6Oi1tcy10cmFjayB7XG4gICAgYmFja2dyb3VuZDogJGJnLWNvbG9yLWRhcms7XG4gICAgYm9yZGVyLXJhZGl1czogJGJvcmRlci1yYWRpdXM7XG4gICAgaGVpZ2h0OiAkdW5pdC1oO1xuICAgIHdpZHRoOiAxMDAlO1xuICB9XG4gICY6Oi1tcy1maWxsLWxvd2VyIHtcbiAgICBiYWNrZ3JvdW5kOiAkcHJpbWFyeS1jb2xvcjtcbiAgfVxufVxuIiwiLy8gVGltZWxpbmVzXG4udGltZWxpbmUge1xuICAudGltZWxpbmUtaXRlbSB7XG4gICAgZGlzcGxheTogZmxleDtcbiAgICBtYXJnaW4tYm90dG9tOiAkdW5pdC02O1xuICAgIHBvc2l0aW9uOiByZWxhdGl2ZTtcbiAgICAmOjpiZWZvcmUge1xuICAgICAgYmFja2dyb3VuZDogJGJvcmRlci1jb2xvcjtcbiAgICAgIGNvbnRlbnQ6IFwiXCI7XG4gICAgICBoZWlnaHQ6IDEwMCU7XG4gICAgICBsZWZ0OiAxMXB4O1xuICAgICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgICAgdG9wOiAkdW5pdC02O1xuICAgICAgd2lkdGg6IDJweDtcbiAgICB9XG5cbiAgICAudGltZWxpbmUtbGVmdCB7XG4gICAgICBmbGV4OiAwIDAgYXV0bztcbiAgICB9XG5cbiAgICAudGltZWxpbmUtY29udGVudCB7XG4gICAgICBmbGV4OiAxIDEgYXV0bztcbiAgICAgIHBhZGRpbmc6IDJweCAwIDJweCAkbGF5b3V0LXNwYWNpbmctbGc7XG4gICAgfVxuXG4gICAgLnRpbWVsaW5lLWljb24ge1xuICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjtcbiAgICAgIGJvcmRlci1yYWRpdXM6IDUwJTtcbiAgICAgIGNvbG9yOiAkbGlnaHQtY29sb3I7XG4gICAgICBkaXNwbGF5OiBmbGV4O1xuICAgICAgaGVpZ2h0OiAkdW5pdC02O1xuICAgICAganVzdGlmeS1jb250ZW50OiBjZW50ZXI7XG4gICAgICB0ZXh0LWFsaWduOiBjZW50ZXI7XG4gICAgICB3aWR0aDogJHVuaXQtNjtcbiAgICAgICY6OmJlZm9yZSB7XG4gICAgICAgIGJvcmRlcjogJGJvcmRlci13aWR0aC1sZyBzb2xpZCAkcHJpbWFyeS1jb2xvcjtcbiAgICAgICAgYm9yZGVyLXJhZGl1czogNTAlO1xuICAgICAgICBjb250ZW50OiBcIlwiO1xuICAgICAgICBkaXNwbGF5OiBibG9jaztcbiAgICAgICAgaGVpZ2h0OiAkdW5pdC0yO1xuICAgICAgICBsZWZ0OiAkdW5pdC0yO1xuICAgICAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgICAgIHRvcDogJHVuaXQtMjtcbiAgICAgICAgd2lkdGg6ICR1bml0LTI7XG4gICAgICB9XG5cbiAgICAgICYuaWNvbi1sZyB7XG4gICAgICAgIGJhY2tncm91bmQ6ICRwcmltYXJ5LWNvbG9yO1xuICAgICAgICBsaW5lLWhlaWdodDogJGxpbmUtaGVpZ2h0O1xuICAgICAgICAmOjpiZWZvcmUge1xuICAgICAgICAgIGNvbnRlbnQ6IG5vbmU7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cbiIsIi8vIDM2MCBEZWdyZWUgVmlld2VyXG5cbi8vIE1peGluOiBWaWV3ZXIgc2xpZGVyIHNpemVzXG5AbWl4aW4gdmlld2VyLXNsaWRlci1zaXplKCRpbWFnZS1udW1iZXI6IDM2KSB7XG4gIEBmb3IgJHMgZnJvbSAxIHRocm91Z2ggKCRpbWFnZS1udW1iZXIpIHtcbiAgICAudmlld2VyLXNsaWRlclttYXg9JyN7JGltYWdlLW51bWJlcn0nXVt2YWx1ZT0nI3skc30nXSArIC52aWV3ZXItaW1hZ2Uge1xuICAgICAgYmFja2dyb3VuZC1wb3NpdGlvbi15OiBwZXJjZW50YWdlKCgoJHMpLTEpICogMS8oKCRpbWFnZS1udW1iZXIpLTEpKTtcbiAgICB9XG4gIH1cbn1cblxuLnZpZXdlci0zNjAge1xuICBhbGlnbi1pdGVtczogY2VudGVyO1xuICBkaXNwbGF5OiBmbGV4O1xuICBmbGV4LWRpcmVjdGlvbjogY29sdW1uO1xuXG4gIC8vIENvcHkgYW5kIGFkZCBtb3JlIG51bWJlcnMgaWYgeW91IG5lZWRcbiAgQGluY2x1ZGUgdmlld2VyLXNsaWRlci1zaXplKDM2KTtcblxuICAudmlld2VyLXNsaWRlciB7XG4gICAgY3Vyc29yOiBldy1yZXNpemU7XG4gICAgbWFyZ2luOiAxcmVtO1xuICAgIG9yZGVyOiAyO1xuICAgIHdpZHRoOiA2MCU7XG4gIH1cblxuICAudmlld2VyLWltYWdlIHtcbiAgICBiYWNrZ3JvdW5kLXBvc2l0aW9uLXk6IDA7XG4gICAgYmFja2dyb3VuZC1yZXBlYXQ6IG5vLXJlcGVhdDtcbiAgICBiYWNrZ3JvdW5kLXNpemU6IDEwMCU7XG4gICAgbWF4LXdpZHRoOiAxMDAlO1xuICAgIG9yZGVyOiAxO1xuICB9XG59Il0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUtBLHNGQUE0RjtBYUo1RixBQUFBLGtCQUFrQixDQUFDLEVBQ2pCLFFBQVEsRUFBRSxRQUFRLEdBNENuQjs7QUE3Q0QsQUFHRSxrQkFIZ0IsQ0FHaEIsd0JBQXdCLENBQUMsRUFDdkIsYUFBYSxFQUFFLFVBQVUsRUFDekIsT0FBTyxFQUFFLElBQUksRUFDYixTQUFTLEVBQUUsSUFBSSxFQUNmLE1BQU0sRUFBRSxJQUFJLEVBQ1osVUFBVSxFWHNETCxNQUFNLEVXckRYLE9BQU8sRVg2Q0YsTUFBSyxHVzVCWDs7QUExQkgsQUFXSSxrQkFYYyxDQUdoQix3QkFBd0IsQUFRckIsV0FBVyxDQUFDLEVIVmYsVUFBVSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQUssQ1REVCx1QkFBTyxFWWFqQixZQUFZLEVaYkYsT0FBTyxHWWNsQjs7QUFkTCxBQWdCSSxrQkFoQmMsQ0FHaEIsd0JBQXdCLENBYXRCLFdBQVcsQ0FBQyxFQUNWLFlBQVksRUFBRSxXQUFXLEVBQ3pCLFVBQVUsRUFBRSxJQUFJLEVBQ2hCLE9BQU8sRUFBRSxZQUFZLEVBQ3JCLElBQUksRUFBRSxRQUFRLEVBQ2QsTUFBTSxFWHVDSCxNQUFNLEVXdENULFdBQVcsRVhvQ1IsTUFBSyxFV25DUixNQUFNLEVYK0JILE1BQUssRVc5QlIsS0FBSyxFQUFFLElBQUksR0FDWjs7QUF6QkwsQUE0QkUsa0JBNUJnQixDQTRCaEIsS0FBSyxDQUFDLEVBQ0osSUFBSSxFQUFFLENBQUMsRUFDUCxRQUFRLEVBQUUsUUFBUSxFQUNsQixHQUFHLEVBQUUsSUFBSSxFQUNULEtBQUssRUFBRSxJQUFJ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QUFtQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sR0FBRyxBQUFULElBQWEsYUFBYSxDQUFtQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sR0FBRyxBQUFULElBQWEsYUFBYSxDQUFtQixFQUNwRSxxQkFBcUIsRUFBRSxHQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sR0FBRyxBQUFULElBQWEsYUFBYSxDQUFtQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxHQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxHQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxHQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxjQUE0QyxHQUNwRTs7QUFJTCxBQU5JLFdBTU8sQ0FOUCxjQUFjLENBQUEsQUFBQSxHQUFDLENBQUksSUFBSSxBQUFSLEVBQVMsQUFBQSxLQUFDLENBQU0sSUFBSSxBQUFWLElBQWMsYUFBYSxDQUFrQixFQUNwRSxxQkFBcUIsRUFBRSxJQUE0QyxHQUNwRTs7QUFJTCxBQVFFLFdBUlMsQ0FRVCxjQUFjLENBQUMsRUFDYixNQUFNLEVBQUUsU0FBUyxFQUNqQixNQUFNLEVBQUUsSUFBSSxFQUNaLEtBQUssRUFBRSxDQUFDLEVBQ1IsS0FBSyxFQUFFLEdBQUcsR0FDWDs7QUFiSCxBQWVFLFdBZlMsQ0FlVCxhQUFhLENBQUMsRUFDWixxQkFBcUIsRUFBRSxDQUFDLEVBQ3hCLGlCQUFpQixFQUFFLFNBQVMsRUFDNUIsZUFBZSxFQUFFLElBQUksRUFDckIsU0FBUyxFQUFFLElBQUksRUFDZixLQUFLLEVBQUUsQ0FBQyxHQUNUIn0= */ diff --git a/user/themes/test/css-compiled/spectre-exp.min.css b/user/themes/test/css-compiled/spectre-exp.min.css new file mode 100755 index 0000000..104787b --- /dev/null +++ b/user/themes/test/css-compiled/spectre-exp.min.css @@ -0,0 +1 @@ +/*! Spectre.css Experimentals v0.5.8 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{display:-ms-flexbox;display:flex;height:auto;min-height:1.6rem;padding:.1rem;-ms-flex-line-pack:start;align-content:flex-start;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#3085ee;box-shadow:0 0 0 .1rem rgba(48,133,238,.2)}.form-autocomplete .form-autocomplete-input .form-input{line-height:.8rem;display:inline-block;width:auto;height:1.2rem;margin:.1rem;border-color:transparent;box-shadow:none;-ms-flex:1 0 auto;flex:1 0 auto}.form-autocomplete .menu{position:absolute;top:100%;left:0;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{overflow-x:auto;-ms-flex-wrap:nowrap;flex-wrap:nowrap}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{display:block;min-width:280px;border:.05rem solid #e7e9ed;border-radius:.1rem}.calendar .calendar-nav{font-size:.9rem;display:-ms-flexbox;display:flex;padding:.4rem;border-top-left-radius:.1rem;border-top-right-radius:.1rem;background:#f8f9fa;-ms-flex-align:center;align-items:center}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;padding:.4rem 0;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:center;justify-content:center}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{max-width:14.28%;-ms-flex:0 0 14.28%;flex:0 0 14.28%}.calendar .calendar-header{font-size:.7rem;text-align:center;color:#acb3c2;border-bottom:.05rem solid #e7e9ed;background:#f8f9fa}.calendar .calendar-body{color:#667189}.calendar .calendar-date{padding:.2rem;border:0}.calendar .calendar-date .date-item{font-size:.7rem;line-height:1rem;position:relative;width:1.4rem;height:1.4rem;padding:.1rem;cursor:pointer;transition:background .2s,border .2s,box-shadow .2s,color .2s;text-align:center;vertical-align:middle;white-space:nowrap;text-decoration:none;color:#667189;border:.05rem solid transparent;border-radius:50%;outline:0;background:0 0;-webkit-appearance:none;-moz-appearance:none;appearance:none}.calendar .calendar-date .date-item.date-today{color:#3085ee;border-color:#d3e5fb}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(48,133,238,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{text-decoration:none;color:#3085ee;border-color:#d3e5fb;background:#eff5fe}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{color:#fff;border-color:#1370e3;background:#227ded}.calendar .calendar-date .date-item.badge::after{position:absolute;top:3px;right:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;pointer-events:none;opacity:.25}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{position:absolute;top:50%;right:0;left:0;height:1.4rem;content:'';transform:translateY(-50%);background:#e1edfd}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{color:#fff;border-color:#1370e3;background:#227ded}.calendar .calendar-range .date-item{color:#3085ee}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{display:-ms-flexbox;display:flex;flex-direction:column;height:5.5rem;padding:0;border-right:.05rem solid #e7e9ed;border-bottom:.05rem solid #e7e9ed;-ms-flex-direction:column}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{height:1.4rem;margin-top:.2rem;margin-right:.2rem;-ms-flex-item-align:end;align-self:flex-end}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{line-height:1;overflow-y:auto;padding:.2rem;-ms-flex-positive:1;flex-grow:1}.calendar.calendar-lg .calendar-event{font-size:.7rem;display:block;overflow:hidden;margin:.1rem auto;padding:3px 4px;white-space:nowrap;text-overflow:ellipsis;border-radius:.1rem}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){z-index:100;animation:carousel-slidein .75s ease-in-out 1;opacity:1}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#e7e9ed}.carousel{position:relative;z-index:1;display:block;overflow:hidden;width:100%;background:#f8f9fa;-webkit-overflow-scrolling:touch}.carousel .carousel-container{position:relative;left:0;height:100%}.carousel .carousel-container::before{display:block;padding-bottom:56.25%;content:''}.carousel .carousel-container .carousel-item{position:absolute;top:0;left:0;width:100%;height:100%;margin:0;animation:carousel-slideout 1s ease-in-out 1;opacity:0}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{position:absolute;z-index:100;top:50%;transition:all .4s;transform:translateY(-50%);opacity:0;color:#e7e9ed;border-color:rgba(231,233,237,.5);background:rgba(231,233,237,.25)}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{position:absolute;z-index:100;bottom:.4rem;left:50%;display:-ms-flexbox;display:flex;width:10rem;transform:translateX(-50%);-ms-flex-pack:center;justify-content:center}.carousel .carousel-nav .nav-item{position:relative;display:block;max-width:2.5rem;height:1.6rem;margin:.2rem;color:rgba(231,233,237,.5);-ms-flex:1 0 auto;flex:1 0 auto}.carousel .carousel-nav .nav-item::before{position:absolute;top:.5rem;display:block;width:100%;height:.1rem;content:'';background:currentColor}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{transform:translateX(0);opacity:1}100%{transform:translateX(-50%);opacity:1}}.comparison-slider{position:relative;overflow:hidden;width:100%;height:50vh;-webkit-overflow-scrolling:touch}.comparison-slider .comparison-after,.comparison-slider .comparison-before{position:absolute;top:0;left:0;overflow:hidden;height:100%;margin:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{position:absolute;width:100%;height:100%;object-fit:cover;object-position:left center}.comparison-slider .comparison-before{z-index:1;width:100%}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{z-index:2;min-width:0;max-width:100%}.comparison-slider .comparison-after::before{position:absolute;z-index:1;top:0;right:.8rem;left:0;height:100%;content:'';cursor:default;background:0 0}.comparison-slider .comparison-after::after{position:absolute;top:50%;right:.4rem;width:3px;height:3px;content:'';transform:translate(50%,-50%);color:#fff;border-radius:50%;background:currentColor;box-shadow:0 -5px,0 5px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{position:relative;top:50%;left:0;width:0;min-width:.8rem;max-width:100%;height:.8rem;resize:horizontal;cursor:ew-resize;transform:translateY(-50%) scaleY(30);animation:first-run 1.5s 1 ease-in-out;opacity:0;outline:0}.comparison-slider .comparison-label{position:absolute;bottom:.8rem;padding:.2rem .4rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;color:#fff;background:rgba(69,77,93,.5)}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{color:#fff;background:#3085ee}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{display:block;width:100%;height:.8rem;border:0;border-radius:.1rem;background:#f8f9fa;-webkit-appearance:none;-moz-appearance:none;appearance:none}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f8f9fa}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{position:relative;display:-ms-flexbox;display:flex;width:100%;height:100%;-ms-flex-flow:nowrap;flex-flow:nowrap}.off-canvas .off-canvas-toggle{position:absolute;z-index:1;top:.4rem;left:.4rem;display:block;transition:none}.off-canvas .off-canvas-sidebar{position:fixed;z-index:200;top:0;bottom:0;left:0;overflow-y:auto;min-width:10rem;transition:transform .25s;transform:translateX(-100%);background:#f8f9fa}.off-canvas .off-canvas-content{height:100%;padding:.4rem .4rem .4rem 4rem;-ms-flex:1 1 auto;flex:1 1 auto}.off-canvas .off-canvas-overlay{position:fixed;top:0;right:0;bottom:0;left:0;display:none;width:100%;height:100%;border-color:transparent;border-radius:0;background:rgba(69,77,93,.1)}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{z-index:100;display:block}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{position:relative;transform:none;-ms-flex:0 0 auto;flex:0 0 auto}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{position:relative;display:block;width:auto;height:auto}.parallax .parallax-content{width:100%;height:auto;transition:all .4s ease;transform:perspective(1000px);box-shadow:0 1rem 2.1rem rgba(69,77,93,.3);transform-style:preserve-3d}.parallax .parallax-content::before{position:absolute;top:0;left:0;display:block;width:100%;height:100%;content:''}.parallax .parallax-front{position:absolute;z-index:1;top:0;left:0;display:-ms-flexbox;display:flex;width:100%;height:100%;transition:transform .4s;transform:translateZ(50px) scale(.95);text-align:center;color:#fff;text-shadow:0 0 20px rgba(69,77,93,.75);-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.parallax .parallax-top-left{position:absolute;z-index:100;top:0;left:0;width:50%;height:50%;outline:0}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{position:absolute;z-index:100;top:0;right:0;width:50%;height:50%;outline:0}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{position:absolute;z-index:100;bottom:0;left:0;width:50%;height:50%;outline:0}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{position:absolute;z-index:100;right:0;bottom:0;width:50%;height:50%;outline:0}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{position:relative;width:100%;height:.2rem;color:#3085ee;border:0;border-radius:.1rem;background:#f0f1f4;-webkit-appearance:none;-moz-appearance:none;appearance:none}.progress::-webkit-progress-bar{border-radius:.1rem;background:0 0}.progress::-webkit-progress-value{border-radius:.1rem;background:#3085ee}.progress::-moz-progress-bar{border-radius:.1rem;background:#3085ee}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#f0f1f4 linear-gradient(to right,#3085ee 30%,#f0f1f4 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{display:block;width:100%;height:1.2rem;background:0 0;-webkit-appearance:none;-moz-appearance:none;appearance:none}.slider:focus{outline:0;box-shadow:0 0 0 .1rem rgba(48,133,238,.2)}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{width:.6rem;height:.6rem;margin-top:-.25rem;transition:transform .2s;border:0;border-radius:50%;background:#3085ee;-webkit-appearance:none}.slider::-moz-range-thumb{width:.6rem;height:.6rem;transition:transform .2s;border:0;border-radius:50%;background:#3085ee}.slider::-ms-thumb{width:.6rem;height:.6rem;transition:transform .2s;border:0;border-radius:50%;background:#3085ee}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{transform:scale(1);background:#e7e9ed}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{transform:scale(1);background:#e7e9ed}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{transform:scale(1);background:#e7e9ed}.slider::-webkit-slider-runnable-track{width:100%;height:.1rem;border-radius:.1rem;background:#f0f1f4}.slider::-moz-range-track{width:100%;height:.1rem;border-radius:.1rem;background:#f0f1f4}.slider::-ms-track{width:100%;height:.1rem;border-radius:.1rem;background:#f0f1f4}.slider::-ms-fill-lower{background:#3085ee}.timeline .timeline-item{position:relative;display:-ms-flexbox;display:flex;margin-bottom:1.2rem}.timeline .timeline-item::before{position:absolute;top:1.2rem;left:11px;width:2px;height:100%;content:'';background:#e7e9ed}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{padding:2px 0 2px .8rem;-ms-flex:1 1 auto;flex:1 1 auto}.timeline .timeline-item .timeline-icon{display:-ms-flexbox;display:flex;width:1.2rem;height:1.2rem;text-align:center;color:#fff;border-radius:50%;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.timeline .timeline-item .timeline-icon::before{position:absolute;top:.4rem;left:.4rem;display:block;width:.4rem;height:.4rem;content:'';border:.1rem solid #3085ee;border-radius:50%}.timeline .timeline-item .timeline-icon.icon-lg{line-height:1.2rem;background:#3085ee}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{display:-ms-flexbox;display:flex;flex-direction:column;-ms-flex-align:center;align-items:center;-ms-flex-direction:column}.viewer-360 .viewer-slider[max='36'][value='1']+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max='36'][value='2']+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max='36'][value='3']+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max='36'][value='4']+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max='36'][value='5']+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max='36'][value='6']+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max='36'][value='7']+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max='36'][value='8']+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max='36'][value='9']+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max='36'][value='10']+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max='36'][value='11']+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max='36'][value='12']+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max='36'][value='13']+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max='36'][value='14']+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max='36'][value='15']+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max='36'][value='16']+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max='36'][value='17']+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max='36'][value='18']+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max='36'][value='19']+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max='36'][value='20']+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max='36'][value='21']+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max='36'][value='22']+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max='36'][value='23']+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max='36'][value='24']+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max='36'][value='25']+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max='36'][value='26']+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max='36'][value='27']+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max='36'][value='28']+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max='36'][value='29']+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max='36'][value='30']+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max='36'][value='31']+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max='36'][value='32']+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max='36'][value='33']+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max='36'][value='34']+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max='36'][value='35']+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max='36'][value='36']+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{width:60%;margin:1rem;cursor:ew-resize;-ms-flex-order:2;order:2}.viewer-360 .viewer-image{max-width:100%;background-repeat:no-repeat;background-position-y:0;background-size:100%;-ms-flex-order:1;order:1} \ No newline at end of file diff --git a/user/themes/test/css-compiled/spectre-icons.css b/user/themes/test/css-compiled/spectre-icons.css new file mode 100755 index 0000000..d968a23 --- /dev/null +++ b/user/themes/test/css-compiled/spectre-icons.css @@ -0,0 +1,172 @@ +/*! Spectre.css Icons v0.5.8 | MIT License | github.com/picturepan2/spectre */ +.icon { box-sizing: border-box; display: inline-block; font-size: inherit; font-style: normal; height: 1em; position: relative; text-indent: -9999px; vertical-align: middle; width: 1em; } + +.icon::before, .icon::after { content: ""; display: block; left: 50%; position: absolute; top: 50%; transform: translate(-50%, -50%); } + +.icon.icon-2x { font-size: 1.6rem; } + +.icon.icon-3x { font-size: 2.4rem; } + +.icon.icon-4x { font-size: 3.2rem; } + +.accordion .icon, .btn .icon, .toast .icon, .menu .icon { vertical-align: -10%; } + +.btn-lg .icon { vertical-align: -15%; } + +.icon-arrow-down::before, .icon-arrow-left::before, .icon-arrow-right::before, .icon-arrow-up::before, .icon-downward::before, .icon-back::before, .icon-forward::before, .icon-upward::before { border: 0.1rem solid currentColor; border-bottom: 0; border-right: 0; height: .65em; width: .65em; } + +.icon-arrow-down::before { transform: translate(-50%, -75%) rotate(225deg); } + +.icon-arrow-left::before { transform: translate(-25%, -50%) rotate(-45deg); } + +.icon-arrow-right::before { transform: translate(-75%, -50%) rotate(135deg); } + +.icon-arrow-up::before { transform: translate(-50%, -25%) rotate(45deg); } + +.icon-back::after, .icon-forward::after { background: currentColor; height: 0.1rem; width: .8em; } + +.icon-downward::after, .icon-upward::after { background: currentColor; height: .8em; width: 0.1rem; } + +.icon-back::after { left: 55%; } + +.icon-back::before { transform: translate(-50%, -50%) rotate(-45deg); } + +.icon-downward::after { top: 45%; } + +.icon-downward::before { transform: translate(-50%, -50%) rotate(-135deg); } + +.icon-forward::after { left: 45%; } + +.icon-forward::before { transform: translate(-50%, -50%) rotate(135deg); } + +.icon-upward::after { top: 55%; } + +.icon-upward::before { transform: translate(-50%, -50%) rotate(45deg); } + +.icon-caret::before { border-top: .3em solid currentColor; border-right: .3em solid transparent; border-left: .3em solid transparent; height: 0; transform: translate(-50%, -25%); width: 0; } + +.icon-menu::before { background: currentColor; box-shadow: 0 -.35em, 0 .35em; height: 0.1rem; width: 100%; } + +.icon-apps::before { background: currentColor; box-shadow: -.35em -.35em, -.35em 0, -.35em .35em, 0 -.35em, 0 .35em, .35em -.35em, .35em 0, .35em .35em; height: 3px; width: 3px; } + +.icon-resize-horiz::before, .icon-resize-horiz::after, .icon-resize-vert::before, .icon-resize-vert::after { border: 0.1rem solid currentColor; border-bottom: 0; border-right: 0; height: .45em; width: .45em; } + +.icon-resize-horiz::before, .icon-resize-vert::before { transform: translate(-50%, -90%) rotate(45deg); } + +.icon-resize-horiz::after, .icon-resize-vert::after { transform: translate(-50%, -10%) rotate(225deg); } + +.icon-resize-horiz::before { transform: translate(-90%, -50%) rotate(-45deg); } + +.icon-resize-horiz::after { transform: translate(-10%, -50%) rotate(135deg); } + +.icon-more-horiz::before, .icon-more-vert::before { background: currentColor; box-shadow: -.4em 0, .4em 0; border-radius: 50%; height: 3px; width: 3px; } + +.icon-more-vert::before { box-shadow: 0 -.4em, 0 .4em; } + +.icon-plus::before, .icon-minus::before, .icon-cross::before { background: currentColor; height: 0.1rem; width: 100%; } + +.icon-plus::after, .icon-cross::after { background: currentColor; height: 100%; width: 0.1rem; } + +.icon-cross::before { width: 100%; } + +.icon-cross::after { height: 100%; } + +.icon-cross::before, .icon-cross::after { transform: translate(-50%, -50%) rotate(45deg); } + +.icon-check::before { border: 0.1rem solid currentColor; border-right: 0; border-top: 0; height: .5em; width: .9em; transform: translate(-50%, -75%) rotate(-45deg); } + +.icon-stop { border: 0.1rem solid currentColor; border-radius: 50%; } + +.icon-stop::before { background: currentColor; height: 0.1rem; transform: translate(-50%, -50%) rotate(45deg); width: 1em; } + +.icon-shutdown { border: 0.1rem solid currentColor; border-radius: 50%; border-top-color: transparent; } + +.icon-shutdown::before { background: currentColor; content: ""; height: .5em; top: .1em; width: 0.1rem; } + +.icon-refresh::before { border: 0.1rem solid currentColor; border-radius: 50%; border-right-color: transparent; height: 1em; width: 1em; } + +.icon-refresh::after { border: .2em solid currentColor; border-top-color: transparent; border-left-color: transparent; height: 0; left: 80%; top: 20%; width: 0; } + +.icon-search::before { border: 0.1rem solid currentColor; border-radius: 50%; height: .75em; left: 5%; top: 5%; transform: translate(0, 0) rotate(45deg); width: .75em; } + +.icon-search::after { background: currentColor; height: 0.1rem; left: 80%; top: 80%; transform: translate(-50%, -50%) rotate(45deg); width: .4em; } + +.icon-edit::before { border: 0.1rem solid currentColor; height: .4em; transform: translate(-40%, -60%) rotate(-45deg); width: .85em; } + +.icon-edit::after { border: .15em solid currentColor; border-top-color: transparent; border-right-color: transparent; height: 0; left: 5%; top: 95%; transform: translate(0, -100%); width: 0; } + +.icon-delete::before { border: 0.1rem solid currentColor; border-bottom-left-radius: 0.1rem; border-bottom-right-radius: 0.1rem; border-top: 0; height: .75em; top: 60%; width: .75em; } + +.icon-delete::after { background: currentColor; box-shadow: -.25em .2em, .25em .2em; height: 0.1rem; top: 0.05rem; width: .5em; } + +.icon-share { border: 0.1rem solid currentColor; border-radius: 0.1rem; border-right: 0; border-top: 0; } + +.icon-share::before { border: 0.1rem solid currentColor; border-left: 0; border-top: 0; height: .4em; left: 100%; top: .25em; transform: translate(-125%, -50%) rotate(-45deg); width: .4em; } + +.icon-share::after { border: 0.1rem solid currentColor; border-bottom: 0; border-right: 0; border-radius: 75% 0; height: .5em; width: .6em; } + +.icon-flag::before { background: currentColor; height: 1em; left: 15%; width: 0.1rem; } + +.icon-flag::after { border: 0.1rem solid currentColor; border-bottom-right-radius: 0.1rem; border-left: 0; border-top-right-radius: 0.1rem; height: .65em; top: 35%; left: 60%; width: .8em; } + +.icon-bookmark::before { border: 0.1rem solid currentColor; border-bottom: 0; border-top-left-radius: 0.1rem; border-top-right-radius: 0.1rem; height: .9em; width: .8em; } + +.icon-bookmark::after { border: 0.1rem solid currentColor; border-bottom: 0; border-left: 0; border-radius: 0.1rem; height: .5em; transform: translate(-50%, 35%) rotate(-45deg) skew(15deg, 15deg); width: .5em; } + +.icon-download, .icon-upload { border-bottom: 0.1rem solid currentColor; } + +.icon-download::before, .icon-upload::before { border: 0.1rem solid currentColor; border-bottom: 0; border-right: 0; height: .5em; width: .5em; transform: translate(-50%, -60%) rotate(-135deg); } + +.icon-download::after, .icon-upload::after { background: currentColor; height: .6em; top: 40%; width: 0.1rem; } + +.icon-upload::before { transform: translate(-50%, -60%) rotate(45deg); } + +.icon-upload::after { top: 50%; } + +.icon-copy::before { border: 0.1rem solid currentColor; border-radius: 0.1rem; border-right: 0; border-bottom: 0; height: .8em; left: 40%; top: 35%; width: .8em; } + +.icon-copy::after { border: 0.1rem solid currentColor; border-radius: 0.1rem; height: .8em; left: 60%; top: 60%; width: .8em; } + +.icon-time { border: 0.1rem solid currentColor; border-radius: 50%; } + +.icon-time::before { background: currentColor; height: .4em; transform: translate(-50%, -75%); width: 0.1rem; } + +.icon-time::after { background: currentColor; height: .3em; transform: translate(-50%, -75%) rotate(90deg); transform-origin: 50% 90%; width: 0.1rem; } + +.icon-mail::before { border: 0.1rem solid currentColor; border-radius: 0.1rem; height: .8em; width: 1em; } + +.icon-mail::after { border: 0.1rem solid currentColor; border-right: 0; border-top: 0; height: .5em; transform: translate(-50%, -90%) rotate(-45deg) skew(10deg, 10deg); width: .5em; } + +.icon-people::before { border: 0.1rem solid currentColor; border-radius: 50%; height: .45em; top: 25%; width: .45em; } + +.icon-people::after { border: 0.1rem solid currentColor; border-radius: 50% 50% 0 0; height: .4em; top: 75%; width: .9em; } + +.icon-message { border: 0.1rem solid currentColor; border-bottom: 0; border-radius: 0.1rem; border-right: 0; } + +.icon-message::before { border: 0.1rem solid currentColor; border-bottom-right-radius: 0.1rem; border-left: 0; border-top: 0; height: .8em; left: 65%; top: 40%; width: .7em; } + +.icon-message::after { background: currentColor; border-radius: 0.1rem; height: .3em; left: 10%; top: 100%; transform: translate(0, -90%) rotate(45deg); width: 0.1rem; } + +.icon-photo { border: 0.1rem solid currentColor; border-radius: 0.1rem; } + +.icon-photo::before { border: 0.1rem solid currentColor; border-radius: 50%; height: .25em; left: 35%; top: 35%; width: .25em; } + +.icon-photo::after { border: 0.1rem solid currentColor; border-bottom: 0; border-left: 0; height: .5em; left: 60%; transform: translate(-50%, 25%) rotate(-45deg); width: .5em; } + +.icon-link::before, .icon-link::after { border: 0.1rem solid currentColor; border-radius: 5em 0 0 5em; border-right: 0; height: .5em; width: .75em; } + +.icon-link::before { transform: translate(-70%, -45%) rotate(-45deg); } + +.icon-link::after { transform: translate(-30%, -55%) rotate(135deg); } + +.icon-location::before { border: 0.1rem solid currentColor; border-radius: 50% 50% 50% 0; height: .8em; transform: translate(-50%, -60%) rotate(-45deg); width: .8em; } + +.icon-location::after { border: 0.1rem solid currentColor; border-radius: 50%; height: .2em; transform: translate(-50%, -80%); width: .2em; } + +.icon-emoji { border: 0.1rem solid currentColor; border-radius: 50%; } + +.icon-emoji::before { border-radius: 50%; box-shadow: -.17em -.1em, .17em -.1em; height: .15em; width: .15em; } + +.icon-emoji::after { border: 0.1rem solid currentColor; border-bottom-color: transparent; border-radius: 50%; border-right-color: transparent; height: .5em; transform: translate(-50%, -40%) rotate(-135deg); width: .5em; } + +/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BlY3RyZS1pY29ucy5jc3MiLCJzb3VyY2VzIjpbInNwZWN0cmUtaWNvbnMuc2NzcyIsInRoZW1lL192YXJpYWJsZXMuc2NzcyIsInNwZWN0cmUvX3ZhcmlhYmxlcy5zY3NzIiwic3BlY3RyZS9fbWl4aW5zLnNjc3MiLCJzcGVjdHJlL21peGlucy9fYXZhdGFyLnNjc3MiLCJzcGVjdHJlL21peGlucy9fYnV0dG9uLnNjc3MiLCJzcGVjdHJlL21peGlucy9fY2xlYXJmaXguc2NzcyIsInNwZWN0cmUvbWl4aW5zL19jb2xvci5zY3NzIiwic3BlY3RyZS9taXhpbnMvX2xhYmVsLnNjc3MiLCJzcGVjdHJlL21peGlucy9fcG9zaXRpb24uc2NzcyIsInNwZWN0cmUvbWl4aW5zL19zaGFkb3cuc2NzcyIsInNwZWN0cmUvbWl4aW5zL190ZXh0LnNjc3MiLCJzcGVjdHJlL21peGlucy9fdG9hc3Quc2NzcyIsInNwZWN0cmUvaWNvbnMvX2ljb25zLWNvcmUuc2NzcyIsInNwZWN0cmUvaWNvbnMvX2ljb25zLW5hdmlnYXRpb24uc2NzcyIsInNwZWN0cmUvaWNvbnMvX2ljb25zLWFjdGlvbi5zY3NzIiwic3BlY3RyZS9pY29ucy9faWNvbnMtb2JqZWN0LnNjc3MiXSwic291cmNlc0NvbnRlbnQiOlsiLy8gVmFyaWFibGVzIGFuZCBtaXhpbnNcbkBpbXBvcnQgXCJ0aGVtZS92YXJpYWJsZXNcIjtcbkBpbXBvcnQgXCJzcGVjdHJlL3ZhcmlhYmxlc1wiO1xuQGltcG9ydCBcInNwZWN0cmUvbWl4aW5zXCI7XG5cbi8qISBTcGVjdHJlLmNzcyBJY29ucyB2I3skdmVyc2lvbn0gfCBNSVQgTGljZW5zZSB8IGdpdGh1Yi5jb20vcGljdHVyZXBhbjIvc3BlY3RyZSAqL1xuLy8gSWNvbnNcbkBpbXBvcnQgXCJzcGVjdHJlL2ljb25zL2ljb25zLWNvcmVcIjtcbkBpbXBvcnQgXCJzcGVjdHJlL2ljb25zL2ljb25zLW5hdmlnYXRpb25cIjtcbkBpbXBvcnQgXCJzcGVjdHJlL2ljb25zL2ljb25zLWFjdGlvblwiO1xuQGltcG9ydCBcInNwZWN0cmUvaWNvbnMvaWNvbnMtb2JqZWN0XCI7XG4iLCIvLyBTcGVjdHJlIE92ZXJyaWRlc1xuJHByaW1hcnktY29sb3I6ICMzMDg1RUUgIWRlZmF1bHQ7XG4kZGFyay1jb2xvcjogIzQ1NGQ1ZCAhZGVmYXVsdDtcbiRsaWdodC1jb2xvcjogI2ZmZiAhZGVmYXVsdDtcbiRncmF5LWNvbG9yOiBsaWdodGVuKCRkYXJrLWNvbG9yLCA0MCUpICFkZWZhdWx0O1xuJGdyYXktY29sb3ItZGFyazogZGFya2VuKCRncmF5LWNvbG9yLCAyNSUpICFkZWZhdWx0O1xuJGJvcmRlci1jb2xvcjogbGlnaHRlbigkZGFyay1jb2xvciwgNjAlKSAhZGVmYXVsdDtcbiRiZy1jb2xvcjogbGlnaHRlbigkZGFyay1jb2xvciwgNjYlKSAhZGVmYXVsdDtcbiRib2R5LWZvbnQtY29sb3I6IGxpZ2h0ZW4oJGRhcmstY29sb3IsIDUlKSAhZGVmYXVsdDtcblxuLy8gTGF5b3V0XG4kaG9yaXotcGFkZGluZzogMXJlbTtcbiR2ZXJ0LXBhZGRpbmc6IDJyZW0gMCAycmVtO1xuXG4vLyBGb250c1xuJG1pbi1yZXNwb25zaXZlLWZvbnQtc2l6ZTogMTZweDtcblxuLy8gSGVhZGVyXG4kaGVhZGVyLWhlaWdodC1sYXJnZTogNHJlbTtcbiRoZWFkZXItaGVpZ2h0LXNtYWxsOiAyLjNyZW07XG4kaGVhZGVyLWNvbG9yLWRhcms6ICMyMjI7XG4kaGVhZGVyLXRleHQtbGlnaHQ6ICRsaWdodC1jb2xvcjtcbiRoZWFkZXItdGV4dC1kYXJrOiBkYXJrZW4oJGRhcmstY29sb3IsIDE1JSk7XG5cbi8vIERyb3Bkb3duIE1lbnVcbiRkcm9wbWVudS1iZzogJGxpZ2h0LWNvbG9yO1xuJGRyb3BtZW51LWhvdmVyLXRleHQ6ICRwcmltYXJ5LWNvbG9yO1xuJGRyb3BtZW51LWhvcml6LXBhZGRpbmc6IDIwcHg7XG4kZHJvcG1lbnUtdmVydC1wYWRkaW5nOiA3cHg7XG4kZHJvcG1lbnUtY2hpbGQtcGFkZGluZzogMTBweDtcbiRkcm9wbWVudS1zaGFkb3c6IDAgM3B4IDVweCByZ2JhKDAsIDAsIDAsIDAuMSk7XG5cbi8vIE1vYmlsZSBNZW51XG4kbW9iaWxlLWNvbG9yLW1haW46ICRwcmltYXJ5LWNvbG9yO1xuJG1vYmlsZS1jb2xvci1hY3RpdmU6ICNGRkY7XG4kbW9iaWxlLWNvbG9yLWxpbms6ICNGRkY7XG4kbW9iaWxlLWJ1dHRvbi1oZWlnaHQ6IDI0cHg7XG4kbW9iaWxlLWJ1dHRvbi13aWR0aDogMjhweDsiLCIvLyBDb3JlIHZhcmlhYmxlc1xuJHZlcnNpb246IFwiMC41LjhcIjtcblxuLy8gQ29yZSBmZWF0dXJlc1xuJHJ0bDogZmFsc2UgIWRlZmF1bHQ7XG5cbi8vIENvcmUgY29sb3JzXG4kcHJpbWFyeS1jb2xvcjogIzU3NTVkOSAhZGVmYXVsdDtcbiRwcmltYXJ5LWNvbG9yLWRhcms6IGRhcmtlbigkcHJpbWFyeS1jb2xvciwgMyUpICFkZWZhdWx0O1xuJHByaW1hcnktY29sb3ItbGlnaHQ6IGxpZ2h0ZW4oJHByaW1hcnktY29sb3IsIDMlKSAhZGVmYXVsdDtcbiRzZWNvbmRhcnktY29sb3I6IGxpZ2h0ZW4oJHByaW1hcnktY29sb3IsIDM3LjUlKSAhZGVmYXVsdDtcbiRzZWNvbmRhcnktY29sb3ItZGFyazogZGFya2VuKCRzZWNvbmRhcnktY29sb3IsIDMlKSAhZGVmYXVsdDtcbiRzZWNvbmRhcnktY29sb3ItbGlnaHQ6IGxpZ2h0ZW4oJHNlY29uZGFyeS1jb2xvciwgMyUpICFkZWZhdWx0O1xuXG4vLyBHcmF5IGNvbG9yc1xuJGRhcmstY29sb3I6ICMzMDM3NDIgIWRlZmF1bHQ7XG4kbGlnaHQtY29sb3I6ICNmZmYgIWRlZmF1bHQ7XG4kZ3JheS1jb2xvcjogbGlnaHRlbigkZGFyay1jb2xvciwgNTUlKSAhZGVmYXVsdDtcbiRncmF5LWNvbG9yLWRhcms6IGRhcmtlbigkZ3JheS1jb2xvciwgMzAlKSAhZGVmYXVsdDtcbiRncmF5LWNvbG9yLWxpZ2h0OiBsaWdodGVuKCRncmF5LWNvbG9yLCAyMCUpICFkZWZhdWx0O1xuXG4kYm9yZGVyLWNvbG9yOiBsaWdodGVuKCRkYXJrLWNvbG9yLCA2NSUpICFkZWZhdWx0O1xuJGJvcmRlci1jb2xvci1kYXJrOiBkYXJrZW4oJGJvcmRlci1jb2xvciwgMTAlKSAhZGVmYXVsdDtcbiRib3JkZXItY29sb3ItbGlnaHQ6IGxpZ2h0ZW4oJGJvcmRlci1jb2xvciwgOCUpICFkZWZhdWx0O1xuJGJnLWNvbG9yOiBsaWdodGVuKCRkYXJrLWNvbG9yLCA3NSUpICFkZWZhdWx0O1xuJGJnLWNvbG9yLWRhcms6IGRhcmtlbigkYmctY29sb3IsIDMlKSAhZGVmYXVsdDtcbiRiZy1jb2xvci1saWdodDogJGxpZ2h0LWNvbG9yICFkZWZhdWx0O1xuXG4vLyBDb250cm9sIGNvbG9yc1xuJHN1Y2Nlc3MtY29sb3I6ICMzMmI2NDMgIWRlZmF1bHQ7XG4kd2FybmluZy1jb2xvcjogI2ZmYjcwMCAhZGVmYXVsdDtcbiRlcnJvci1jb2xvcjogI2U4NTYwMCAhZGVmYXVsdDtcblxuLy8gT3RoZXIgY29sb3JzXG4kY29kZS1jb2xvcjogI2Q3M2U0OCAhZGVmYXVsdDtcbiRoaWdobGlnaHQtY29sb3I6ICNmZmU5YjMgIWRlZmF1bHQ7XG4kYm9keS1iZzogJGJnLWNvbG9yLWxpZ2h0ICFkZWZhdWx0O1xuJGJvZHktZm9udC1jb2xvcjogbGlnaHRlbigkZGFyay1jb2xvciwgNSUpICFkZWZhdWx0O1xuJGxpbmstY29sb3I6ICRwcmltYXJ5LWNvbG9yICFkZWZhdWx0O1xuJGxpbmstY29sb3ItZGFyazogZGFya2VuKCRsaW5rLWNvbG9yLCAxMCUpICFkZWZhdWx0O1xuJGxpbmstY29sb3ItbGlnaHQ6IGxpZ2h0ZW4oJGxpbmstY29sb3IsIDEwJSkgIWRlZmF1bHQ7XG5cbi8vIEZvbnRzXG4vLyBDcmVkaXQ6IGh0dHBzOi8vd3d3LnNtYXNoaW5nbWFnYXppbmUuY29tLzIwMTUvMTEvdXNpbmctc3lzdGVtLXVpLWZvbnRzLXByYWN0aWNhbC1ndWlkZS9cbiRiYXNlLWZvbnQtZmFtaWx5OiAtYXBwbGUtc3lzdGVtLCBzeXN0ZW0tdWksIEJsaW5rTWFjU3lzdGVtRm9udCwgXCJTZWdvZSBVSVwiLCBSb2JvdG8gIWRlZmF1bHQ7XG4kbW9uby1mb250LWZhbWlseTogXCJTRiBNb25vXCIsIFwiU2Vnb2UgVUkgTW9ub1wiLCBcIlJvYm90byBNb25vXCIsIE1lbmxvLCBDb3VyaWVyLCBtb25vc3BhY2UgIWRlZmF1bHQ7XG4kZmFsbGJhY2stZm9udC1mYW1pbHk6IFwiSGVsdmV0aWNhIE5ldWVcIiwgc2Fucy1zZXJpZiAhZGVmYXVsdDtcbiRjamstemgtaGFucy1mb250LWZhbWlseTogJGJhc2UtZm9udC1mYW1pbHksIFwiUGluZ0ZhbmcgU0NcIiwgXCJIaXJhZ2lubyBTYW5zIEdCXCIsIFwiTWljcm9zb2Z0IFlhSGVpXCIsICRmYWxsYmFjay1mb250LWZhbWlseSAhZGVmYXVsdDtcbiRjamstemgtaGFudC1mb250LWZhbWlseTogJGJhc2UtZm9udC1mYW1pbHksIFwiUGluZ0ZhbmcgVENcIiwgXCJIaXJhZ2lubyBTYW5zIENOU1wiLCBcIk1pY3Jvc29mdCBKaGVuZ0hlaVwiLCAkZmFsbGJhY2stZm9udC1mYW1pbHkgIWRlZmF1bHQ7XG4kY2prLWpwLWZvbnQtZmFtaWx5OiAkYmFzZS1mb250LWZhbWlseSwgXCJIaXJhZ2lubyBTYW5zXCIsIFwiSGlyYWdpbm8gS2FrdSBHb3RoaWMgUHJvXCIsIFwiWXUgR290aGljXCIsIFl1R290aGljLCBNZWlyeW8sICRmYWxsYmFjay1mb250LWZhbWlseSAhZGVmYXVsdDtcbiRjamsta28tZm9udC1mYW1pbHk6ICRiYXNlLWZvbnQtZmFtaWx5LCBcIk1hbGd1biBHb3RoaWNcIiwgJGZhbGxiYWNrLWZvbnQtZmFtaWx5ICFkZWZhdWx0O1xuJGJvZHktZm9udC1mYW1pbHk6ICRiYXNlLWZvbnQtZmFtaWx5LCAkZmFsbGJhY2stZm9udC1mYW1pbHkgIWRlZmF1bHQ7XG5cbi8vIFVuaXQgc2l6ZXNcbiR1bml0LW86IC4wNXJlbSAhZGVmYXVsdDtcbiR1bml0LWg6IC4xcmVtICFkZWZhdWx0O1xuJHVuaXQtMTogLjJyZW0gIWRlZmF1bHQ7XG4kdW5pdC0yOiAuNHJlbSAhZGVmYXVsdDtcbiR1bml0LTM6IC42cmVtICFkZWZhdWx0O1xuJHVuaXQtNDogLjhyZW0gIWRlZmF1bHQ7XG4kdW5pdC01OiAxcmVtICFkZWZhdWx0O1xuJHVuaXQtNjogMS4ycmVtICFkZWZhdWx0O1xuJHVuaXQtNzogMS40cmVtICFkZWZhdWx0O1xuJHVuaXQtODogMS42cmVtICFkZWZhdWx0O1xuJHVuaXQtOTogMS44cmVtICFkZWZhdWx0O1xuJHVuaXQtMTA6IDJyZW0gIWRlZmF1bHQ7XG4kdW5pdC0xMjogMi40cmVtICFkZWZhdWx0O1xuJHVuaXQtMTY6IDMuMnJlbSAhZGVmYXVsdDtcblxuLy8gRm9udCBzaXplc1xuJGh0bWwtZm9udC1zaXplOiAyMHB4ICFkZWZhdWx0O1xuJGh0bWwtbGluZS1oZWlnaHQ6IDEuNSAhZGVmYXVsdDtcbiRmb250LXNpemU6IC44cmVtICFkZWZhdWx0O1xuJGZvbnQtc2l6ZS1zbTogLjdyZW0gIWRlZmF1bHQ7XG4kZm9udC1zaXplLWxnOiAuOXJlbSAhZGVmYXVsdDtcbiRsaW5lLWhlaWdodDogMS4ycmVtICFkZWZhdWx0O1xuXG4vLyBTaXplc1xuJGxheW91dC1zcGFjaW5nOiAkdW5pdC0yICFkZWZhdWx0O1xuJGxheW91dC1zcGFjaW5nLXNtOiAkdW5pdC0xICFkZWZhdWx0O1xuJGxheW91dC1zcGFjaW5nLWxnOiAkdW5pdC00ICFkZWZhdWx0O1xuJGJvcmRlci1yYWRpdXM6ICR1bml0LWggIWRlZmF1bHQ7XG4kYm9yZGVyLXdpZHRoOiAkdW5pdC1vICFkZWZhdWx0O1xuJGJvcmRlci13aWR0aC1sZzogJHVuaXQtaCAhZGVmYXVsdDtcbiRjb250cm9sLXNpemU6ICR1bml0LTkgIWRlZmF1bHQ7XG4kY29udHJvbC1zaXplLXNtOiAkdW5pdC03ICFkZWZhdWx0O1xuJGNvbnRyb2wtc2l6ZS1sZzogJHVuaXQtMTAgIWRlZmF1bHQ7XG4kY29udHJvbC1wYWRkaW5nLXg6ICR1bml0LTIgIWRlZmF1bHQ7XG4kY29udHJvbC1wYWRkaW5nLXgtc206ICR1bml0LTIgKiAuNzUgIWRlZmF1bHQ7XG4kY29udHJvbC1wYWRkaW5nLXgtbGc6ICR1bml0LTIgKiAxLjUgIWRlZmF1bHQ7XG4kY29udHJvbC1wYWRkaW5nLXk6ICgkY29udHJvbC1zaXplIC0gJGxpbmUtaGVpZ2h0KSAvIDIgLSAkYm9yZGVyLXdpZHRoICFkZWZhdWx0O1xuJGNvbnRyb2wtcGFkZGluZy15LXNtOiAoJGNvbnRyb2wtc2l6ZS1zbSAtICRsaW5lLWhlaWdodCkgLyAyIC0gJGJvcmRlci13aWR0aCAhZGVmYXVsdDtcbiRjb250cm9sLXBhZGRpbmcteS1sZzogKCRjb250cm9sLXNpemUtbGcgLSAkbGluZS1oZWlnaHQpIC8gMiAtICRib3JkZXItd2lkdGggIWRlZmF1bHQ7XG4kY29udHJvbC1pY29uLXNpemU6IC44cmVtICFkZWZhdWx0O1xuXG4kY29udHJvbC13aWR0aC14czogMTgwcHggIWRlZmF1bHQ7XG4kY29udHJvbC13aWR0aC1zbTogMzIwcHggIWRlZmF1bHQ7XG4kY29udHJvbC13aWR0aC1tZDogNjQwcHggIWRlZmF1bHQ7XG4kY29udHJvbC13aWR0aC1sZzogOTYwcHggIWRlZmF1bHQ7XG4kY29udHJvbC13aWR0aC14bDogMTI4MHB4ICFkZWZhdWx0O1xuXG4vLyBSZXNwb25zaXZlIGJyZWFrcG9pbnRzXG4kc2l6ZS14czogNDgwcHggIWRlZmF1bHQ7XG4kc2l6ZS1zbTogNjAwcHggIWRlZmF1bHQ7XG4kc2l6ZS1tZDogODQwcHggIWRlZmF1bHQ7XG4kc2l6ZS1sZzogOTYwcHggIWRlZmF1bHQ7XG4kc2l6ZS14bDogMTI4MHB4ICFkZWZhdWx0O1xuJHNpemUtMng6IDE0NDBweCAhZGVmYXVsdDtcblxuJHJlc3BvbnNpdmUtYnJlYWtwb2ludDogJHNpemUteHMgIWRlZmF1bHQ7XG5cbi8vIFotaW5kZXhcbiR6aW5kZXgtMDogMSAhZGVmYXVsdDtcbiR6aW5kZXgtMTogMTAwICFkZWZhdWx0O1xuJHppbmRleC0yOiAyMDAgIWRlZmF1bHQ7XG4kemluZGV4LTM6IDMwMCAhZGVmYXVsdDtcbiR6aW5kZXgtNDogNDAwICFkZWZhdWx0O1xuIiwiLy8gTWl4aW5zXG5AaW1wb3J0IFwibWl4aW5zL2F2YXRhclwiO1xuQGltcG9ydCBcIm1peGlucy9idXR0b25cIjtcbkBpbXBvcnQgXCJtaXhpbnMvY2xlYXJmaXhcIjtcbkBpbXBvcnQgXCJtaXhpbnMvY29sb3JcIjtcbkBpbXBvcnQgXCJtaXhpbnMvbGFiZWxcIjtcbkBpbXBvcnQgXCJtaXhpbnMvcG9zaXRpb25cIjtcbkBpbXBvcnQgXCJtaXhpbnMvc2hhZG93XCI7XG5AaW1wb3J0IFwibWl4aW5zL3RleHRcIjtcbkBpbXBvcnQgXCJtaXhpbnMvdG9hc3RcIjsiLCIvLyBBdmF0YXIgbWl4aW5cbkBtaXhpbiBhdmF0YXItYmFzZSgkc2l6ZTogJHVuaXQtOCkge1xuICBmb250LXNpemU6ICRzaXplIC8gMjtcbiAgaGVpZ2h0OiAkc2l6ZTtcbiAgd2lkdGg6ICRzaXplO1xufVxuIiwiLy8gQnV0dG9uIHZhcmlhbnQgbWl4aW5cbkBtaXhpbiBidXR0b24tdmFyaWFudCgkY29sb3I6ICRwcmltYXJ5LWNvbG9yKSB7XG4gIGJhY2tncm91bmQ6ICRjb2xvcjtcbiAgYm9yZGVyLWNvbG9yOiBkYXJrZW4oJGNvbG9yLCAzJSk7XG4gIGNvbG9yOiAkbGlnaHQtY29sb3I7XG4gICY6Zm9jdXMge1xuICAgIEBpbmNsdWRlIGNvbnRyb2wtc2hhZG93KCRjb2xvcik7XG4gIH1cbiAgJjpmb2N1cyxcbiAgJjpob3ZlciB7XG4gICAgYmFja2dyb3VuZDogZGFya2VuKCRjb2xvciwgMiUpO1xuICAgIGJvcmRlci1jb2xvcjogZGFya2VuKCRjb2xvciwgNSUpO1xuICAgIGNvbG9yOiAkbGlnaHQtY29sb3I7XG4gIH1cbiAgJjphY3RpdmUsXG4gICYuYWN0aXZlIHtcbiAgICBiYWNrZ3JvdW5kOiBkYXJrZW4oJGNvbG9yLCA3JSk7XG4gICAgYm9yZGVyLWNvbG9yOiBkYXJrZW4oJGNvbG9yLCAxMCUpO1xuICAgIGNvbG9yOiAkbGlnaHQtY29sb3I7XG4gIH1cbiAgJi5sb2FkaW5nIHtcbiAgICAmOjphZnRlciB7XG4gICAgICBib3JkZXItYm90dG9tLWNvbG9yOiAkbGlnaHQtY29sb3I7XG4gICAgICBib3JkZXItbGVmdC1jb2xvcjogJGxpZ2h0LWNvbG9yO1xuICAgIH1cbiAgfVxufVxuXG5AbWl4aW4gYnV0dG9uLW91dGxpbmUtdmFyaWFudCgkY29sb3I6ICRwcmltYXJ5LWNvbG9yKSB7XG4gIGJhY2tncm91bmQ6ICRsaWdodC1jb2xvcjtcbiAgYm9yZGVyLWNvbG9yOiAkY29sb3I7XG4gIGNvbG9yOiAkY29sb3I7XG4gICY6Zm9jdXMge1xuICAgIEBpbmNsdWRlIGNvbnRyb2wtc2hhZG93KCRjb2xvcik7XG4gIH1cbiAgJjpmb2N1cyxcbiAgJjpob3ZlciB7XG4gICAgYmFja2dyb3VuZDogbGlnaHRlbigkY29sb3IsIDUwJSk7XG4gICAgYm9yZGVyLWNvbG9yOiBkYXJrZW4oJGNvbG9yLCAyJSk7XG4gICAgY29sb3I6ICRjb2xvcjtcbiAgfVxuICAmOmFjdGl2ZSxcbiAgJi5hY3RpdmUge1xuICAgIGJhY2tncm91bmQ6ICRjb2xvcjtcbiAgICBib3JkZXItY29sb3I6IGRhcmtlbigkY29sb3IsIDUlKTtcbiAgICBjb2xvcjogJGxpZ2h0LWNvbG9yO1xuICB9XG4gICYubG9hZGluZyB7XG4gICAgJjo6YWZ0ZXIge1xuICAgICAgYm9yZGVyLWJvdHRvbS1jb2xvcjogJGNvbG9yO1xuICAgICAgYm9yZGVyLWxlZnQtY29sb3I6ICRjb2xvcjtcbiAgICB9XG4gIH1cbn1cbiIsIi8vIENsZWFyZml4IG1peGluXG5AbWl4aW4gY2xlYXJmaXgoKSB7XG4gICY6OmFmdGVyIHtcbiAgICBjbGVhcjogYm90aDtcbiAgICBjb250ZW50OiBcIlwiO1xuICAgIGRpc3BsYXk6IHRhYmxlO1xuICB9XG59XG4iLCIvLyBCYWNrZ3JvdW5kIGNvbG9yIHV0aWxpdHkgbWl4aW5cbkBtaXhpbiBiZy1jb2xvci12YXJpYW50KCRuYW1lOiBcIi5iZy1wcmltYXJ5XCIsICRjb2xvcjogJHByaW1hcnktY29sb3IpIHtcbiAgI3skbmFtZX0ge1xuICAgIGJhY2tncm91bmQ6ICRjb2xvciAhaW1wb3J0YW50O1xuXG4gICAgQGlmIChsaWdodG5lc3MoJGNvbG9yKSA8IDYwKSB7XG4gICAgICBjb2xvcjogJGxpZ2h0LWNvbG9yO1xuICAgIH1cbiAgfVxufVxuXG4vLyBUZXh0IGNvbG9yIHV0aWxpdHkgbWl4aW5cbkBtaXhpbiB0ZXh0LWNvbG9yLXZhcmlhbnQoJG5hbWU6IFwiLnRleHQtcHJpbWFyeVwiLCAkY29sb3I6ICRwcmltYXJ5LWNvbG9yKSB7XG4gICN7JG5hbWV9IHtcbiAgICBjb2xvcjogJGNvbG9yICFpbXBvcnRhbnQ7XG4gIH1cblxuICBhI3skbmFtZX0ge1xuICAgICY6Zm9jdXMsXG4gICAgJjpob3ZlciB7XG4gICAgICBjb2xvcjogZGFya2VuKCRjb2xvciwgNSUpO1xuICAgIH1cbiAgICAmOnZpc2l0ZWQge1xuICAgICAgY29sb3I6IGxpZ2h0ZW4oJGNvbG9yLCA1JSk7XG4gICAgfVxuICB9XG59XG4iLCIvLyBMYWJlbCBiYXNlIHN0eWxlXG5AbWl4aW4gbGFiZWwtYmFzZSgpIHtcbiAgYm9yZGVyLXJhZGl1czogJGJvcmRlci1yYWRpdXM7XG4gIGxpbmUtaGVpZ2h0OiAxLjI1O1xuICBwYWRkaW5nOiAuMXJlbSAuMnJlbTtcbn1cblxuQG1peGluIGxhYmVsLXZhcmlhbnQoJGNvbG9yOiAkbGlnaHQtY29sb3IsICRiZy1jb2xvcjogJHByaW1hcnktY29sb3IpIHtcbiAgYmFja2dyb3VuZDogJGJnLWNvbG9yO1xuICBjb2xvcjogJGNvbG9yO1xufVxuIiwiLy8gTWFyZ2luIHV0aWxpdHkgbWl4aW5cbkBtaXhpbiBtYXJnaW4tdmFyaWFudCgkaWQ6IDEsICRzaXplOiAkdW5pdC0xKSB7XG4gIC5tLSN7JGlkfSB7XG4gICAgbWFyZ2luOiAkc2l6ZSAhaW1wb3J0YW50O1xuICB9XG5cbiAgLm1iLSN7JGlkfSB7XG4gICAgbWFyZ2luLWJvdHRvbTogJHNpemUgIWltcG9ydGFudDtcbiAgfVxuXG4gIC5tbC0jeyRpZH0ge1xuICAgIG1hcmdpbi1sZWZ0OiAkc2l6ZSAhaW1wb3J0YW50O1xuICB9XG5cbiAgLm1yLSN7JGlkfSB7XG4gICAgbWFyZ2luLXJpZ2h0OiAkc2l6ZSAhaW1wb3J0YW50O1xuICB9XG5cbiAgLm10LSN7JGlkfSB7XG4gICAgbWFyZ2luLXRvcDogJHNpemUgIWltcG9ydGFudDtcbiAgfVxuXG4gIC5teC0jeyRpZH0ge1xuICAgIG1hcmdpbi1sZWZ0OiAkc2l6ZSAhaW1wb3J0YW50O1xuICAgIG1hcmdpbi1yaWdodDogJHNpemUgIWltcG9ydGFudDtcbiAgfVxuXG4gIC5teS0jeyRpZH0ge1xuICAgIG1hcmdpbi1ib3R0b206ICRzaXplICFpbXBvcnRhbnQ7XG4gICAgbWFyZ2luLXRvcDogJHNpemUgIWltcG9ydGFudDtcbiAgfVxufVxuXG4vLyBQYWRkaW5nIHV0aWxpdHkgbWl4aW5cbkBtaXhpbiBwYWRkaW5nLXZhcmlhbnQoJGlkOiAxLCAkc2l6ZTogJHVuaXQtMSkge1xuICAucC0jeyRpZH0ge1xuICAgIHBhZGRpbmc6ICRzaXplICFpbXBvcnRhbnQ7XG4gIH1cblxuICAucGItI3skaWR9IHtcbiAgICBwYWRkaW5nLWJvdHRvbTogJHNpemUgIWltcG9ydGFudDtcbiAgfVxuXG4gIC5wbC0jeyRpZH0ge1xuICAgIHBhZGRpbmctbGVmdDogJHNpemUgIWltcG9ydGFudDtcbiAgfVxuXG4gIC5wci0jeyRpZH0ge1xuICAgIHBhZGRpbmctcmlnaHQ6ICRzaXplICFpbXBvcnRhbnQ7XG4gIH1cblxuICAucHQtI3skaWR9IHtcbiAgICBwYWRkaW5nLXRvcDogJHNpemUgIWltcG9ydGFudDtcbiAgfVxuXG4gIC5weC0jeyRpZH0ge1xuICAgIHBhZGRpbmctbGVmdDogJHNpemUgIWltcG9ydGFudDtcbiAgICBwYWRkaW5nLXJpZ2h0OiAkc2l6ZSAhaW1wb3J0YW50O1xuICB9XG4gIFxuICAucHktI3skaWR9IHtcbiAgICBwYWRkaW5nLWJvdHRvbTogJHNpemUgIWltcG9ydGFudDtcbiAgICBwYWRkaW5nLXRvcDogJHNpemUgIWltcG9ydGFudDtcbiAgfVxufVxuIiwiLy8gQ29tcG9uZW50IGZvY3VzIHNoYWRvd1xuQG1peGluIGNvbnRyb2wtc2hhZG93KCRjb2xvcjogJHByaW1hcnktY29sb3IpIHtcbiAgYm94LXNoYWRvdzogMCAwIDAgLjFyZW0gcmdiYSgkY29sb3IsIC4yKTtcbn1cblxuLy8gU2hhZG93IG1peGluXG5AbWl4aW4gc2hhZG93LXZhcmlhbnQoJG9mZnNldCkge1xuICBib3gtc2hhZG93OiAwICRvZmZzZXQgKCRvZmZzZXQgKyAuMDVyZW0pICogMiByZ2JhKCRkYXJrLWNvbG9yLCAuMyk7XG59XG4iLCIvLyBUZXh0IEVsbGlwc2lzXG5AbWl4aW4gdGV4dC1lbGxpcHNpcygpIHtcbiAgb3ZlcmZsb3c6IGhpZGRlbjtcbiAgdGV4dC1vdmVyZmxvdzogZWxsaXBzaXM7XG4gIHdoaXRlLXNwYWNlOiBub3dyYXA7XG59XG4iLCIvLyBUb2FzdCB2YXJpYW50IG1peGluXG5AbWl4aW4gdG9hc3QtdmFyaWFudCgkY29sb3I6ICRkYXJrLWNvbG9yKSB7XG4gIGJhY2tncm91bmQ6IHJnYmEoJGNvbG9yLCAuOTUpO1xuICBib3JkZXItY29sb3I6ICRjb2xvcjtcbn1cbiIsIi8vIEljb24gdmFyaWFibGVzXG4kaWNvbi1ib3JkZXItd2lkdGg6ICRib3JkZXItd2lkdGgtbGc7XG4kaWNvbi1wcmVmaXg6IFwiaWNvblwiO1xuXG4vLyBJY29uIGJhc2Ugc3R5bGVcbi4jeyRpY29uLXByZWZpeH0ge1xuICBib3gtc2l6aW5nOiBib3JkZXItYm94O1xuICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XG4gIGZvbnQtc2l6ZTogaW5oZXJpdDtcbiAgZm9udC1zdHlsZTogbm9ybWFsO1xuICBoZWlnaHQ6IDFlbTtcbiAgcG9zaXRpb246IHJlbGF0aXZlO1xuICB0ZXh0LWluZGVudDogLTk5OTlweDtcbiAgdmVydGljYWwtYWxpZ246IG1pZGRsZTtcbiAgd2lkdGg6IDFlbTtcbiAgJjo6YmVmb3JlLFxuICAmOjphZnRlciB7XG4gICAgY29udGVudDogXCJcIjtcbiAgICBkaXNwbGF5OiBibG9jaztcbiAgICBsZWZ0OiA1MCU7XG4gICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgIHRvcDogNTAlO1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlKC01MCUsIC01MCUpO1xuICB9XG5cbiAgLy8gSWNvbiBzaXplc1xuICAmLmljb24tMngge1xuICAgIGZvbnQtc2l6ZTogMS42cmVtO1xuICB9XG5cbiAgJi5pY29uLTN4IHtcbiAgICBmb250LXNpemU6IDIuNHJlbTtcbiAgfVxuXG4gICYuaWNvbi00eCB7XG4gICAgZm9udC1zaXplOiAzLjJyZW07XG4gIH1cbn1cblxuLy8gQ29tcG9uZW50IGljb24gc3VwcG9ydFxuLmFjY29yZGlvbixcbi5idG4sXG4udG9hc3QsXG4ubWVudSB7XG4gIC4jeyRpY29uLXByZWZpeH0ge1xuICAgIHZlcnRpY2FsLWFsaWduOiAtMTAlO1xuICB9XG59XG5cbi5idG4tbGcge1xuICAuI3skaWNvbi1wcmVmaXh9IHtcbiAgICB2ZXJ0aWNhbC1hbGlnbjogLTE1JTtcbiAgfVxufVxuIiwiLy8gSWNvbiBhcnJvd3Ncbi5pY29uLWFycm93LWRvd24sXG4uaWNvbi1hcnJvdy1sZWZ0LFxuLmljb24tYXJyb3ctcmlnaHQsXG4uaWNvbi1hcnJvdy11cCxcbi5pY29uLWRvd253YXJkLFxuLmljb24tYmFjayxcbi5pY29uLWZvcndhcmQsXG4uaWNvbi11cHdhcmQge1xuICAmOjpiZWZvcmUge1xuICAgIGJvcmRlcjogJGljb24tYm9yZGVyLXdpZHRoIHNvbGlkIGN1cnJlbnRDb2xvcjtcbiAgICBib3JkZXItYm90dG9tOiAwO1xuICAgIGJvcmRlci1yaWdodDogMDtcbiAgICBoZWlnaHQ6IC42NWVtO1xuICAgIHdpZHRoOiAuNjVlbTtcbiAgfVxufVxuXG4uaWNvbi1hcnJvdy1kb3duIHtcbiAgJjo6YmVmb3JlIHtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtNTAlLCAtNzUlKSByb3RhdGUoMjI1ZGVnKTtcbiAgfVxufVxuXG4uaWNvbi1hcnJvdy1sZWZ0IHtcbiAgJjo6YmVmb3JlIHtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtMjUlLCAtNTAlKSByb3RhdGUoLTQ1ZGVnKTtcbiAgfVxufVxuXG4uaWNvbi1hcnJvdy1yaWdodCB7XG4gICY6OmJlZm9yZSB7XG4gICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUoLTc1JSwgLTUwJSkgcm90YXRlKDEzNWRlZyk7XG4gIH1cbn1cblxuLmljb24tYXJyb3ctdXAge1xuICAmOjpiZWZvcmUge1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlKC01MCUsIC0yNSUpIHJvdGF0ZSg0NWRlZyk7XG4gIH1cbn1cblxuLmljb24tYmFjayxcbi5pY29uLWZvcndhcmQge1xuICAmOjphZnRlciB7XG4gICAgYmFja2dyb3VuZDogY3VycmVudENvbG9yO1xuICAgIGhlaWdodDogJGljb24tYm9yZGVyLXdpZHRoO1xuICAgIHdpZHRoOiAuOGVtO1xuICB9XG59XG5cbi5pY29uLWRvd253YXJkLFxuLmljb24tdXB3YXJkIHtcbiAgJjo6YWZ0ZXIge1xuICAgIGJhY2tncm91bmQ6IGN1cnJlbnRDb2xvcjtcbiAgICBoZWlnaHQ6IC44ZW07XG4gICAgd2lkdGg6ICRpY29uLWJvcmRlci13aWR0aDtcbiAgfVxufVxuXG4uaWNvbi1iYWNrIHtcbiAgJjo6YWZ0ZXIge1xuICAgIGxlZnQ6IDU1JTtcbiAgfVxuICAmOjpiZWZvcmUge1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlKC01MCUsIC01MCUpIHJvdGF0ZSgtNDVkZWcpO1xuICB9XG59XG5cbi5pY29uLWRvd253YXJkIHtcbiAgJjo6YWZ0ZXIge1xuICAgIHRvcDogNDUlO1xuICB9XG4gICY6OmJlZm9yZSB7XG4gICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUoLTUwJSwgLTUwJSkgcm90YXRlKC0xMzVkZWcpO1xuICB9XG59XG5cbi5pY29uLWZvcndhcmQge1xuICAmOjphZnRlciB7XG4gICAgbGVmdDogNDUlO1xuICB9XG4gICY6OmJlZm9yZSB7XG4gICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUoLTUwJSwgLTUwJSkgcm90YXRlKDEzNWRlZyk7XG4gIH1cbn1cblxuLmljb24tdXB3YXJkIHtcbiAgJjo6YWZ0ZXIge1xuICAgIHRvcDogNTUlO1xuICB9XG4gICY6OmJlZm9yZSB7XG4gICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUoLTUwJSwgLTUwJSkgcm90YXRlKDQ1ZGVnKTtcbiAgfVxufVxuXG4vLyBJY29uIGNhcmV0XG4uaWNvbi1jYXJldCB7XG4gICY6OmJlZm9yZSB7XG4gICAgYm9yZGVyLXRvcDogLjNlbSBzb2xpZCBjdXJyZW50Q29sb3I7XG4gICAgYm9yZGVyLXJpZ2h0OiAuM2VtIHNvbGlkIHRyYW5zcGFyZW50O1xuICAgIGJvcmRlci1sZWZ0OiAuM2VtIHNvbGlkIHRyYW5zcGFyZW50O1xuICAgIGhlaWdodDogMDtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtNTAlLCAtMjUlKTtcbiAgICB3aWR0aDogMDtcbiAgfVxufVxuXG4vLyBJY29uIG1lbnVcbi5pY29uLW1lbnUge1xuICAmOjpiZWZvcmUge1xuICAgIGJhY2tncm91bmQ6IGN1cnJlbnRDb2xvcjtcbiAgICBib3gtc2hhZG93OiAwIC0uMzVlbSwgMCAuMzVlbTtcbiAgICBoZWlnaHQ6ICRpY29uLWJvcmRlci13aWR0aDtcbiAgICB3aWR0aDogMTAwJTtcbiAgfVxufVxuXG4vLyBJY29uIGFwcHNcbi5pY29uLWFwcHMge1xuICAmOjpiZWZvcmUge1xuICAgIGJhY2tncm91bmQ6IGN1cnJlbnRDb2xvcjtcbiAgICBib3gtc2hhZG93OiAtLjM1ZW0gLS4zNWVtLCAtLjM1ZW0gMCwgLS4zNWVtIC4zNWVtLCAwIC0uMzVlbSwgMCAuMzVlbSwgLjM1ZW0gLS4zNWVtLCAuMzVlbSAwLCAuMzVlbSAuMzVlbTtcbiAgICBoZWlnaHQ6IDNweDtcbiAgICB3aWR0aDogM3B4O1xuICB9XG59XG4iLCIvLyBJY29uIHJlc2l6ZVxuLmljb24tcmVzaXplLWhvcml6LFxuLmljb24tcmVzaXplLXZlcnQge1xuICAmOjpiZWZvcmUsXG4gICY6OmFmdGVyIHtcbiAgICBib3JkZXI6ICRpY29uLWJvcmRlci13aWR0aCBzb2xpZCBjdXJyZW50Q29sb3I7XG4gICAgYm9yZGVyLWJvdHRvbTogMDtcbiAgICBib3JkZXItcmlnaHQ6IDA7XG4gICAgaGVpZ2h0OiAuNDVlbTtcbiAgICB3aWR0aDogLjQ1ZW07XG4gIH1cbiAgJjo6YmVmb3JlIHtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtNTAlLCAtOTAlKSByb3RhdGUoNDVkZWcpO1xuICB9XG4gICY6OmFmdGVyIHtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtNTAlLCAtMTAlKSByb3RhdGUoMjI1ZGVnKTtcbiAgfVxufVxuXG4uaWNvbi1yZXNpemUtaG9yaXoge1xuICAmOjpiZWZvcmUge1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlKC05MCUsIC01MCUpIHJvdGF0ZSgtNDVkZWcpO1xuICB9XG4gICY6OmFmdGVyIHtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtMTAlLCAtNTAlKSByb3RhdGUoMTM1ZGVnKTtcbiAgfVxufVxuXG4vLyBJY29uIG1vcmVcbi5pY29uLW1vcmUtaG9yaXosXG4uaWNvbi1tb3JlLXZlcnQge1xuICAmOjpiZWZvcmUge1xuICAgIGJhY2tncm91bmQ6IGN1cnJlbnRDb2xvcjtcbiAgICBib3gtc2hhZG93OiAtLjRlbSAwLCAuNGVtIDA7XG4gICAgYm9yZGVyLXJhZGl1czogNTAlO1xuICAgIGhlaWdodDogM3B4O1xuICAgIHdpZHRoOiAzcHg7XG4gIH1cbn1cblxuLmljb24tbW9yZS12ZXJ0IHtcbiAgJjo6YmVmb3JlIHtcbiAgICBib3gtc2hhZG93OiAwIC0uNGVtLCAwIC40ZW07XG4gIH1cbn1cblxuLy8gSWNvbiBwbHVzLCBtaW51cywgY3Jvc3Ncbi5pY29uLXBsdXMsXG4uaWNvbi1taW51cyxcbi5pY29uLWNyb3NzIHtcbiAgJjo6YmVmb3JlIHtcbiAgICBiYWNrZ3JvdW5kOiBjdXJyZW50Q29sb3I7XG4gICAgaGVpZ2h0OiAkaWNvbi1ib3JkZXItd2lkdGg7XG4gICAgd2lkdGg6IDEwMCU7XG4gIH1cbn1cblxuLmljb24tcGx1cyxcbi5pY29uLWNyb3NzIHtcbiAgJjo6YWZ0ZXIge1xuICAgIGJhY2tncm91bmQ6IGN1cnJlbnRDb2xvcjtcbiAgICBoZWlnaHQ6IDEwMCU7XG4gICAgd2lkdGg6ICRpY29uLWJvcmRlci13aWR0aDtcbiAgfVxufVxuXG4uaWNvbi1jcm9zcyB7XG4gICY6OmJlZm9yZSB7XG4gICAgd2lkdGg6IDEwMCU7XG4gIH1cbiAgJjo6YWZ0ZXIge1xuICAgIGhlaWdodDogMTAwJTtcbiAgfVxuICAmOjpiZWZvcmUsXG4gICY6OmFmdGVyIHtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtNTAlLCAtNTAlKSByb3RhdGUoNDVkZWcpO1xuICB9XG59XG5cbi8vIEljb24gY2hlY2tcbi5pY29uLWNoZWNrIHtcbiAgJjo6YmVmb3JlIHtcbiAgICBib3JkZXI6ICRpY29uLWJvcmRlci13aWR0aCBzb2xpZCBjdXJyZW50Q29sb3I7XG4gICAgYm9yZGVyLXJpZ2h0OiAwO1xuICAgIGJvcmRlci10b3A6IDA7XG4gICAgaGVpZ2h0OiAuNWVtO1xuICAgIHdpZHRoOiAuOWVtO1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlKC01MCUsIC03NSUpIHJvdGF0ZSgtNDVkZWcpO1xuICB9XG59XG5cbi8vIEljb24gc3RvcFxuLmljb24tc3RvcCB7XG4gIGJvcmRlcjogJGljb24tYm9yZGVyLXdpZHRoIHNvbGlkIGN1cnJlbnRDb2xvcjtcbiAgYm9yZGVyLXJhZGl1czogNTAlO1xuICAmOjpiZWZvcmUge1xuICAgIGJhY2tncm91bmQ6IGN1cnJlbnRDb2xvcjtcbiAgICBoZWlnaHQ6ICRpY29uLWJvcmRlci13aWR0aDtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtNTAlLCAtNTAlKSByb3RhdGUoNDVkZWcpO1xuICAgIHdpZHRoOiAxZW07XG4gIH1cbn1cblxuLy8gSWNvbiBzaHV0ZG93blxuLmljb24tc2h1dGRvd24ge1xuICBib3JkZXI6ICRpY29uLWJvcmRlci13aWR0aCBzb2xpZCBjdXJyZW50Q29sb3I7XG4gIGJvcmRlci1yYWRpdXM6IDUwJTtcbiAgYm9yZGVyLXRvcC1jb2xvcjogdHJhbnNwYXJlbnQ7XG4gICY6OmJlZm9yZSB7XG4gICAgYmFja2dyb3VuZDogY3VycmVudENvbG9yO1xuICAgIGNvbnRlbnQ6IFwiXCI7XG4gICAgaGVpZ2h0OiAuNWVtO1xuICAgIHRvcDogLjFlbTtcbiAgICB3aWR0aDogJGljb24tYm9yZGVyLXdpZHRoO1xuICB9XG59XG5cbi8vIEljb24gcmVmcmVzaFxuLmljb24tcmVmcmVzaCB7XG4gICY6OmJlZm9yZSB7XG4gICAgYm9yZGVyOiAkaWNvbi1ib3JkZXItd2lkdGggc29saWQgY3VycmVudENvbG9yO1xuICAgIGJvcmRlci1yYWRpdXM6IDUwJTtcbiAgICBib3JkZXItcmlnaHQtY29sb3I6IHRyYW5zcGFyZW50O1xuICAgIGhlaWdodDogMWVtO1xuICAgIHdpZHRoOiAxZW07XG4gIH1cbiAgJjo6YWZ0ZXIge1xuICAgIGJvcmRlcjogLjJlbSBzb2xpZCBjdXJyZW50Q29sb3I7XG4gICAgYm9yZGVyLXRvcC1jb2xvcjogdHJhbnNwYXJlbnQ7XG4gICAgYm9yZGVyLWxlZnQtY29sb3I6IHRyYW5zcGFyZW50O1xuICAgIGhlaWdodDogMDtcbiAgICBsZWZ0OiA4MCU7XG4gICAgdG9wOiAyMCU7XG4gICAgd2lkdGg6IDA7XG4gIH1cbn1cblxuLy8gSWNvbiBzZWFyY2hcbi5pY29uLXNlYXJjaCB7XG4gICY6OmJlZm9yZSB7XG4gICAgYm9yZGVyOiAkaWNvbi1ib3JkZXItd2lkdGggc29saWQgY3VycmVudENvbG9yO1xuICAgIGJvcmRlci1yYWRpdXM6IDUwJTtcbiAgICBoZWlnaHQ6IC43NWVtO1xuICAgIGxlZnQ6IDUlO1xuICAgIHRvcDogNSU7XG4gICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUoMCwgMCkgcm90YXRlKDQ1ZGVnKTtcbiAgICB3aWR0aDogLjc1ZW07XG4gIH1cbiAgJjo6YWZ0ZXIge1xuICAgIGJhY2tncm91bmQ6IGN1cnJlbnRDb2xvcjtcbiAgICBoZWlnaHQ6ICRpY29uLWJvcmRlci13aWR0aDtcbiAgICBsZWZ0OiA4MCU7XG4gICAgdG9wOiA4MCU7XG4gICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUoLTUwJSwgLTUwJSkgcm90YXRlKDQ1ZGVnKTtcbiAgICB3aWR0aDogLjRlbTtcbiAgfVxufVxuXG4vLyBJY29uIGVkaXRcbi5pY29uLWVkaXQge1xuICAmOjpiZWZvcmUge1xuICAgIGJvcmRlcjogJGljb24tYm9yZGVyLXdpZHRoIHNvbGlkIGN1cnJlbnRDb2xvcjtcbiAgICBoZWlnaHQ6IC40ZW07XG4gICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUoLTQwJSwgLTYwJSkgcm90YXRlKC00NWRlZyk7XG4gICAgd2lkdGg6IC44NWVtO1xuICB9XG4gICY6OmFmdGVyIHtcbiAgICBib3JkZXI6IC4xNWVtIHNvbGlkIGN1cnJlbnRDb2xvcjtcbiAgICBib3JkZXItdG9wLWNvbG9yOiB0cmFuc3BhcmVudDtcbiAgICBib3JkZXItcmlnaHQtY29sb3I6IHRyYW5zcGFyZW50O1xuICAgIGhlaWdodDogMDtcbiAgICBsZWZ0OiA1JTtcbiAgICB0b3A6IDk1JTtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgwLCAtMTAwJSk7XG4gICAgd2lkdGg6IDA7XG4gIH1cbn1cblxuLy8gSWNvbiBkZWxldGVcbi5pY29uLWRlbGV0ZSB7XG4gICY6OmJlZm9yZSB7XG4gICAgYm9yZGVyOiAkaWNvbi1ib3JkZXItd2lkdGggc29saWQgY3VycmVudENvbG9yO1xuICAgIGJvcmRlci1ib3R0b20tbGVmdC1yYWRpdXM6ICRib3JkZXItcmFkaXVzO1xuICAgIGJvcmRlci1ib3R0b20tcmlnaHQtcmFkaXVzOiAkYm9yZGVyLXJhZGl1cztcbiAgICBib3JkZXItdG9wOiAwO1xuICAgIGhlaWdodDogLjc1ZW07XG4gICAgdG9wOiA2MCU7XG4gICAgd2lkdGg6IC43NWVtO1xuICB9XG4gICY6OmFmdGVyIHtcbiAgICBiYWNrZ3JvdW5kOiBjdXJyZW50Q29sb3I7XG4gICAgYm94LXNoYWRvdzogLS4yNWVtIC4yZW0sIC4yNWVtIC4yZW07XG4gICAgaGVpZ2h0OiAkaWNvbi1ib3JkZXItd2lkdGg7XG4gICAgdG9wOiAkaWNvbi1ib3JkZXItd2lkdGgvMjtcbiAgICB3aWR0aDogLjVlbTtcbiAgfVxufVxuXG4vLyBJY29uIHNoYXJlXG4uaWNvbi1zaGFyZSB7XG4gIGJvcmRlcjogJGljb24tYm9yZGVyLXdpZHRoIHNvbGlkIGN1cnJlbnRDb2xvcjtcbiAgYm9yZGVyLXJhZGl1czogJGJvcmRlci1yYWRpdXM7XG4gIGJvcmRlci1yaWdodDogMDtcbiAgYm9yZGVyLXRvcDogMDtcbiAgJjo6YmVmb3JlIHtcbiAgICBib3JkZXI6ICRpY29uLWJvcmRlci13aWR0aCBzb2xpZCBjdXJyZW50Q29sb3I7XG4gICAgYm9yZGVyLWxlZnQ6IDA7XG4gICAgYm9yZGVyLXRvcDogMDtcbiAgICBoZWlnaHQ6IC40ZW07XG4gICAgbGVmdDogMTAwJTtcbiAgICB0b3A6IC4yNWVtO1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlKC0xMjUlLCAtNTAlKSByb3RhdGUoLTQ1ZGVnKTtcbiAgICB3aWR0aDogLjRlbTtcbiAgfVxuICAmOjphZnRlciB7XG4gICAgYm9yZGVyOiAkaWNvbi1ib3JkZXItd2lkdGggc29saWQgY3VycmVudENvbG9yO1xuICAgIGJvcmRlci1ib3R0b206IDA7XG4gICAgYm9yZGVyLXJpZ2h0OiAwO1xuICAgIGJvcmRlci1yYWRpdXM6IDc1JSAwO1xuICAgIGhlaWdodDogLjVlbTtcbiAgICB3aWR0aDogLjZlbTtcbiAgfVxufVxuXG4vLyBJY29uIGZsYWdcbi5pY29uLWZsYWcge1xuICAmOjpiZWZvcmUge1xuICAgIGJhY2tncm91bmQ6IGN1cnJlbnRDb2xvcjtcbiAgICBoZWlnaHQ6IDFlbTtcbiAgICBsZWZ0OiAxNSU7XG4gICAgd2lkdGg6ICRpY29uLWJvcmRlci13aWR0aDtcbiAgfVxuICAmOjphZnRlciB7XG4gICAgYm9yZGVyOiAkaWNvbi1ib3JkZXItd2lkdGggc29saWQgY3VycmVudENvbG9yO1xuICAgIGJvcmRlci1ib3R0b20tcmlnaHQtcmFkaXVzOiAkYm9yZGVyLXJhZGl1cztcbiAgICBib3JkZXItbGVmdDogMDtcbiAgICBib3JkZXItdG9wLXJpZ2h0LXJhZGl1czogJGJvcmRlci1yYWRpdXM7XG4gICAgaGVpZ2h0OiAuNjVlbTtcbiAgICB0b3A6IDM1JTtcbiAgICBsZWZ0OiA2MCU7XG4gICAgd2lkdGg6IC44ZW07XG4gIH1cbn1cblxuLy8gSWNvbiBib29rbWFya1xuLmljb24tYm9va21hcmsge1xuICAmOjpiZWZvcmUge1xuICAgIGJvcmRlcjogJGljb24tYm9yZGVyLXdpZHRoIHNvbGlkIGN1cnJlbnRDb2xvcjtcbiAgICBib3JkZXItYm90dG9tOiAwO1xuICAgIGJvcmRlci10b3AtbGVmdC1yYWRpdXM6ICRib3JkZXItcmFkaXVzO1xuICAgIGJvcmRlci10b3AtcmlnaHQtcmFkaXVzOiAkYm9yZGVyLXJhZGl1cztcbiAgICBoZWlnaHQ6IC45ZW07XG4gICAgd2lkdGg6IC44ZW07XG4gIH1cbiAgJjo6YWZ0ZXIge1xuICAgIGJvcmRlcjogJGljb24tYm9yZGVyLXdpZHRoIHNvbGlkIGN1cnJlbnRDb2xvcjtcbiAgICBib3JkZXItYm90dG9tOiAwO1xuICAgIGJvcmRlci1sZWZ0OiAwO1xuICAgIGJvcmRlci1yYWRpdXM6ICRib3JkZXItcmFkaXVzO1xuICAgIGhlaWdodDogLjVlbTtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtNTAlLCAzNSUpIHJvdGF0ZSgtNDVkZWcpIHNrZXcoMTVkZWcsIDE1ZGVnKTtcbiAgICB3aWR0aDogLjVlbTtcbiAgfVxufVxuXG4vLyBJY29uIGRvd25sb2FkICYgdXBsb2FkXG4uaWNvbi1kb3dubG9hZCxcbi5pY29uLXVwbG9hZCB7XG4gIGJvcmRlci1ib3R0b206ICRpY29uLWJvcmRlci13aWR0aCBzb2xpZCBjdXJyZW50Q29sb3I7XG4gICY6OmJlZm9yZSB7XG4gICAgYm9yZGVyOiAkaWNvbi1ib3JkZXItd2lkdGggc29saWQgY3VycmVudENvbG9yO1xuICAgIGJvcmRlci1ib3R0b206IDA7XG4gICAgYm9yZGVyLXJpZ2h0OiAwO1xuICAgIGhlaWdodDogLjVlbTtcbiAgICB3aWR0aDogLjVlbTtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtNTAlLCAtNjAlKSByb3RhdGUoLTEzNWRlZyk7XG4gIH1cbiAgJjo6YWZ0ZXIge1xuICAgIGJhY2tncm91bmQ6IGN1cnJlbnRDb2xvcjtcbiAgICBoZWlnaHQ6IC42ZW07XG4gICAgdG9wOiA0MCU7XG4gICAgd2lkdGg6ICRpY29uLWJvcmRlci13aWR0aDtcbiAgfVxufVxuXG4uaWNvbi11cGxvYWQge1xuICAmOjpiZWZvcmUge1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlKC01MCUsIC02MCUpIHJvdGF0ZSg0NWRlZyk7XG4gIH1cbiAgJjo6YWZ0ZXIge1xuICAgIHRvcDogNTAlO1xuICB9XG59XG5cbi8vIEljb24gY29weVxuLmljb24tY29weSB7XG4gICY6OmJlZm9yZSB7XG4gICAgYm9yZGVyOiAkaWNvbi1ib3JkZXItd2lkdGggc29saWQgY3VycmVudENvbG9yO1xuICAgIGJvcmRlci1yYWRpdXM6ICRib3JkZXItcmFkaXVzO1xuICAgIGJvcmRlci1yaWdodDogMDtcbiAgICBib3JkZXItYm90dG9tOiAwO1xuICAgIGhlaWdodDogLjhlbTtcbiAgICBsZWZ0OiA0MCU7XG4gICAgdG9wOiAzNSU7XG4gICAgd2lkdGg6IC44ZW07XG4gIH1cbiAgJjo6YWZ0ZXIge1xuICAgIGJvcmRlcjogJGljb24tYm9yZGVyLXdpZHRoIHNvbGlkIGN1cnJlbnRDb2xvcjtcbiAgICBib3JkZXItcmFkaXVzOiAkYm9yZGVyLXJhZGl1cztcbiAgICBoZWlnaHQ6IC44ZW07XG4gICAgbGVmdDogNjAlO1xuICAgIHRvcDogNjAlO1xuICAgIHdpZHRoOiAuOGVtO1xuICB9XG59IiwiLy8gSWNvbiB0aW1lXG4uaWNvbi10aW1lIHtcbiAgYm9yZGVyOiAkaWNvbi1ib3JkZXItd2lkdGggc29saWQgY3VycmVudENvbG9yO1xuICBib3JkZXItcmFkaXVzOiA1MCU7XG4gICY6OmJlZm9yZSB7XG4gICAgYmFja2dyb3VuZDogY3VycmVudENvbG9yO1xuICAgIGhlaWdodDogLjRlbTtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtNTAlLCAtNzUlKTtcbiAgICB3aWR0aDogJGljb24tYm9yZGVyLXdpZHRoO1xuICB9XG4gICY6OmFmdGVyIHtcbiAgICBiYWNrZ3JvdW5kOiBjdXJyZW50Q29sb3I7XG4gICAgaGVpZ2h0OiAuM2VtO1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlKC01MCUsIC03NSUpIHJvdGF0ZSg5MGRlZyk7XG4gICAgdHJhbnNmb3JtLW9yaWdpbjogNTAlIDkwJTtcbiAgICB3aWR0aDogJGljb24tYm9yZGVyLXdpZHRoO1xuICB9XG59XG5cbi8vIEljb24gbWFpbFxuLmljb24tbWFpbCB7XG4gICY6OmJlZm9yZSB7XG4gICAgYm9yZGVyOiAkaWNvbi1ib3JkZXItd2lkdGggc29saWQgY3VycmVudENvbG9yO1xuICAgIGJvcmRlci1yYWRpdXM6ICRib3JkZXItcmFkaXVzO1xuICAgIGhlaWdodDogLjhlbTtcbiAgICB3aWR0aDogMWVtO1xuICB9XG4gICY6OmFmdGVyIHtcbiAgICBib3JkZXI6ICRpY29uLWJvcmRlci13aWR0aCBzb2xpZCBjdXJyZW50Q29sb3I7XG4gICAgYm9yZGVyLXJpZ2h0OiAwO1xuICAgIGJvcmRlci10b3A6IDA7XG4gICAgaGVpZ2h0OiAuNWVtO1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlKC01MCUsIC05MCUpIHJvdGF0ZSgtNDVkZWcpIHNrZXcoMTBkZWcsIDEwZGVnKTtcbiAgICB3aWR0aDogLjVlbTtcbiAgfVxufVxuXG4vLyBJY29uIHBlb3BsZVxuLmljb24tcGVvcGxlIHtcbiAgJjo6YmVmb3JlIHtcbiAgICBib3JkZXI6ICRpY29uLWJvcmRlci13aWR0aCBzb2xpZCBjdXJyZW50Q29sb3I7XG4gICAgYm9yZGVyLXJhZGl1czogNTAlO1xuICAgIGhlaWdodDogLjQ1ZW07XG4gICAgdG9wOiAyNSU7XG4gICAgd2lkdGg6IC40NWVtO1xuICB9XG4gICY6OmFmdGVyIHtcbiAgICBib3JkZXI6ICRpY29uLWJvcmRlci13aWR0aCBzb2xpZCBjdXJyZW50Q29sb3I7XG4gICAgYm9yZGVyLXJhZGl1czogNTAlIDUwJSAwIDA7XG4gICAgaGVpZ2h0OiAuNGVtO1xuICAgIHRvcDogNzUlO1xuICAgIHdpZHRoOiAuOWVtO1xuICB9XG59XG5cbi8vIEljb24gbWVzc2FnZVxuLmljb24tbWVzc2FnZSB7XG4gIGJvcmRlcjogJGljb24tYm9yZGVyLXdpZHRoIHNvbGlkIGN1cnJlbnRDb2xvcjtcbiAgYm9yZGVyLWJvdHRvbTogMDtcbiAgYm9yZGVyLXJhZGl1czogJGJvcmRlci1yYWRpdXM7XG4gIGJvcmRlci1yaWdodDogMDtcbiAgJjo6YmVmb3JlIHtcbiAgICBib3JkZXI6ICRpY29uLWJvcmRlci13aWR0aCBzb2xpZCBjdXJyZW50Q29sb3I7XG4gICAgYm9yZGVyLWJvdHRvbS1yaWdodC1yYWRpdXM6ICRib3JkZXItcmFkaXVzO1xuICAgIGJvcmRlci1sZWZ0OiAwO1xuICAgIGJvcmRlci10b3A6IDA7XG4gICAgaGVpZ2h0OiAuOGVtO1xuICAgIGxlZnQ6IDY1JTtcbiAgICB0b3A6IDQwJTtcbiAgICB3aWR0aDogLjdlbTtcbiAgfVxuICAmOjphZnRlciB7XG4gICAgYmFja2dyb3VuZDogY3VycmVudENvbG9yO1xuICAgIGJvcmRlci1yYWRpdXM6ICRib3JkZXItcmFkaXVzO1xuICAgIGhlaWdodDogLjNlbTtcbiAgICBsZWZ0OiAxMCU7XG4gICAgdG9wOiAxMDAlO1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlKDAsIC05MCUpIHJvdGF0ZSg0NWRlZyk7XG4gICAgd2lkdGg6ICRpY29uLWJvcmRlci13aWR0aDtcbiAgfVxufVxuXG4vLyBJY29uIHBob3RvXG4uaWNvbi1waG90byB7XG4gIGJvcmRlcjogJGljb24tYm9yZGVyLXdpZHRoIHNvbGlkIGN1cnJlbnRDb2xvcjtcbiAgYm9yZGVyLXJhZGl1czogJGJvcmRlci1yYWRpdXM7XG4gICY6OmJlZm9yZSB7XG4gICAgYm9yZGVyOiAkaWNvbi1ib3JkZXItd2lkdGggc29saWQgY3VycmVudENvbG9yO1xuICAgIGJvcmRlci1yYWRpdXM6IDUwJTtcbiAgICBoZWlnaHQ6IC4yNWVtO1xuICAgIGxlZnQ6IDM1JTtcbiAgICB0b3A6IDM1JTtcbiAgICB3aWR0aDogLjI1ZW07XG4gIH1cbiAgJjo6YWZ0ZXIge1xuICAgIGJvcmRlcjogJGljb24tYm9yZGVyLXdpZHRoIHNvbGlkIGN1cnJlbnRDb2xvcjtcbiAgICBib3JkZXItYm90dG9tOiAwO1xuICAgIGJvcmRlci1sZWZ0OiAwO1xuICAgIGhlaWdodDogLjVlbTtcbiAgICBsZWZ0OiA2MCU7XG4gICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUoLTUwJSwgMjUlKSByb3RhdGUoLTQ1ZGVnKTtcbiAgICB3aWR0aDogLjVlbTtcbiAgfVxufVxuXG4vLyBJY29uIGxpbmtcbi5pY29uLWxpbmsge1xuICAmOjpiZWZvcmUsXG4gICY6OmFmdGVyIHtcbiAgICBib3JkZXI6ICRpY29uLWJvcmRlci13aWR0aCBzb2xpZCBjdXJyZW50Q29sb3I7XG4gICAgYm9yZGVyLXJhZGl1czogNWVtIDAgMCA1ZW07XG4gICAgYm9yZGVyLXJpZ2h0OiAwO1xuICAgIGhlaWdodDogLjVlbTtcbiAgICB3aWR0aDogLjc1ZW07XG4gIH1cbiAgJjo6YmVmb3JlIHtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtNzAlLCAtNDUlKSByb3RhdGUoLTQ1ZGVnKTtcbiAgfVxuICAmOjphZnRlciB7XG4gICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUoLTMwJSwgLTU1JSkgcm90YXRlKDEzNWRlZyk7XG4gIH1cbn1cblxuLy8gSWNvbiBsb2NhdGlvblxuLmljb24tbG9jYXRpb24ge1xuICAmOjpiZWZvcmUge1xuICAgIGJvcmRlcjogJGljb24tYm9yZGVyLXdpZHRoIHNvbGlkIGN1cnJlbnRDb2xvcjtcbiAgICBib3JkZXItcmFkaXVzOiA1MCUgNTAlIDUwJSAwO1xuICAgIGhlaWdodDogLjhlbTtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtNTAlLCAtNjAlKSByb3RhdGUoLTQ1ZGVnKTtcbiAgICB3aWR0aDogLjhlbTtcbiAgfVxuICAmOjphZnRlciB7XG4gICAgYm9yZGVyOiAkaWNvbi1ib3JkZXItd2lkdGggc29saWQgY3VycmVudENvbG9yO1xuICAgIGJvcmRlci1yYWRpdXM6IDUwJTtcbiAgICBoZWlnaHQ6IC4yZW07XG4gICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUoLTUwJSwgLTgwJSk7XG4gICAgd2lkdGg6IC4yZW07XG4gIH1cbn1cblxuLy8gSWNvbiBlbW9qaVxuLmljb24tZW1vamkge1xuICBib3JkZXI6ICRpY29uLWJvcmRlci13aWR0aCBzb2xpZCBjdXJyZW50Q29sb3I7XG4gIGJvcmRlci1yYWRpdXM6IDUwJTtcbiAgJjo6YmVmb3JlIHtcbiAgICBib3JkZXItcmFkaXVzOiA1MCU7XG4gICAgYm94LXNoYWRvdzogLS4xN2VtIC0uMWVtLCAuMTdlbSAtLjFlbTtcbiAgICBoZWlnaHQ6IC4xNWVtO1xuICAgIHdpZHRoOiAuMTVlbTtcbiAgfVxuICAmOjphZnRlciB7XG4gICAgYm9yZGVyOiAkaWNvbi1ib3JkZXItd2lkdGggc29saWQgY3VycmVudENvbG9yO1xuICAgIGJvcmRlci1ib3R0b20tY29sb3I6IHRyYW5zcGFyZW50O1xuICAgIGJvcmRlci1yYWRpdXM6IDUwJTtcbiAgICBib3JkZXItcmlnaHQtY29sb3I6IHRyYW5zcGFyZW50O1xuICAgIGhlaWdodDogLjVlbTtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtNTAlLCAtNDAlKSByb3RhdGUoLTEzNWRlZyk7XG4gICAg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 */ diff --git a/user/themes/test/css-compiled/spectre-icons.min.css b/user/themes/test/css-compiled/spectre-icons.min.css new file mode 100755 index 0000000..8f00a92 --- /dev/null +++ b/user/themes/test/css-compiled/spectre-icons.min.css @@ -0,0 +1 @@ +/*! Spectre.css Icons v0.5.8 | MIT License | github.com/picturepan2/spectre */.icon{font-size:inherit;font-style:normal;position:relative;display:inline-block;box-sizing:border-box;width:1em;height:1em;vertical-align:middle;text-indent:-9999px}.icon::after,.icon::before{position:absolute;top:50%;left:50%;display:block;content:'';transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{width:.65em;height:.65em;border:.1rem solid currentColor;border-right:0;border-bottom:0}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{width:.8em;height:.1rem;background:currentColor}.icon-downward::after,.icon-upward::after{width:.1rem;height:.8em;background:currentColor}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{width:0;height:0;transform:translate(-50%,-25%);border-top:.3em solid currentColor;border-right:.3em solid transparent;border-left:.3em solid transparent}.icon-menu::before{width:100%;height:.1rem;background:currentColor;box-shadow:0 -.35em,0 .35em}.icon-apps::before{width:3px;height:3px;background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{width:.45em;height:.45em;border:.1rem solid currentColor;border-right:0;border-bottom:0}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{width:3px;height:3px;border-radius:50%;background:currentColor;box-shadow:-.4em 0,.4em 0}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{width:100%;height:.1rem;background:currentColor}.icon-cross::after,.icon-plus::after{width:.1rem;height:100%;background:currentColor}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{width:.9em;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);border:.1rem solid currentColor;border-top:0;border-right:0}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{width:1em;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);background:currentColor}.icon-shutdown{border:.1rem solid currentColor;border-top-color:transparent;border-radius:50%}.icon-shutdown::before{top:.1em;width:.1rem;height:.5em;content:'';background:currentColor}.icon-refresh::before{width:1em;height:1em;border:.1rem solid currentColor;border-right-color:transparent;border-radius:50%}.icon-refresh::after{top:20%;left:80%;width:0;height:0;border:.2em solid currentColor;border-top-color:transparent;border-left-color:transparent}.icon-search::before{top:5%;left:5%;width:.75em;height:.75em;transform:translate(0,0) rotate(45deg);border:.1rem solid currentColor;border-radius:50%}.icon-search::after{top:80%;left:80%;width:.4em;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);background:currentColor}.icon-edit::before{width:.85em;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);border:.1rem solid currentColor}.icon-edit::after{top:95%;left:5%;width:0;height:0;transform:translate(0,-100%);border:.15em solid currentColor;border-top-color:transparent;border-right-color:transparent}.icon-delete::before{top:60%;width:.75em;height:.75em;border:.1rem solid currentColor;border-top:0;border-bottom-right-radius:.1rem;border-bottom-left-radius:.1rem}.icon-delete::after{top:.05rem;width:.5em;height:.1rem;background:currentColor;box-shadow:-.25em .2em,.25em .2em}.icon-share{border:.1rem solid currentColor;border-top:0;border-right:0;border-radius:.1rem}.icon-share::before{top:.25em;left:100%;width:.4em;height:.4em;transform:translate(-125%,-50%) rotate(-45deg);border:.1rem solid currentColor;border-top:0;border-left:0}.icon-share::after{width:.6em;height:.5em;border:.1rem solid currentColor;border-right:0;border-bottom:0;border-radius:75% 0}.icon-flag::before{left:15%;width:.1rem;height:1em;background:currentColor}.icon-flag::after{top:35%;left:60%;width:.8em;height:.65em;border:.1rem solid currentColor;border-left:0;border-top-right-radius:.1rem;border-bottom-right-radius:.1rem}.icon-bookmark::before{width:.8em;height:.9em;border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem}.icon-bookmark::after{width:.5em;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{width:.5em;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);border:.1rem solid currentColor;border-right:0;border-bottom:0}.icon-download::after,.icon-upload::after{top:40%;width:.1rem;height:.6em;background:currentColor}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{top:35%;left:40%;width:.8em;height:.8em;border:.1rem solid currentColor;border-right:0;border-bottom:0;border-radius:.1rem}.icon-copy::after{top:60%;left:60%;width:.8em;height:.8em;border:.1rem solid currentColor;border-radius:.1rem}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{width:.1rem;height:.4em;transform:translate(-50%,-75%);background:currentColor}.icon-time::after{width:.1rem;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;background:currentColor}.icon-mail::before{width:1em;height:.8em;border:.1rem solid currentColor;border-radius:.1rem}.icon-mail::after{width:.5em;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);border:.1rem solid currentColor;border-top:0;border-right:0}.icon-people::before{top:25%;width:.45em;height:.45em;border:.1rem solid currentColor;border-radius:50%}.icon-people::after{top:75%;width:.9em;height:.4em;border:.1rem solid currentColor;border-radius:50% 50% 0 0}.icon-message{border:.1rem solid currentColor;border-right:0;border-bottom:0;border-radius:.1rem}.icon-message::before{top:40%;left:65%;width:.7em;height:.8em;border:.1rem solid currentColor;border-top:0;border-left:0;border-bottom-right-radius:.1rem}.icon-message::after{top:100%;left:10%;width:.1rem;height:.3em;transform:translate(0,-90%) rotate(45deg);border-radius:.1rem;background:currentColor}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{top:35%;left:35%;width:.25em;height:.25em;border:.1rem solid currentColor;border-radius:50%}.icon-photo::after{left:60%;width:.5em;height:.5em;transform:translate(-50%,25%) rotate(-45deg);border:.1rem solid currentColor;border-bottom:0;border-left:0}.icon-link::after,.icon-link::before{width:.75em;height:.5em;border:.1rem solid currentColor;border-right:0;border-radius:5em 0 0 5em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{width:.8em;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);border:.1rem solid currentColor;border-radius:50% 50% 50% 0}.icon-location::after{width:.2em;height:.2em;transform:translate(-50%,-80%);border:.1rem solid currentColor;border-radius:50%}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{width:.15em;height:.15em;border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em}.icon-emoji::after{width:.5em;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);border:.1rem solid currentColor;border-right-color:transparent;border-bottom-color:transparent;border-radius:50%} \ No newline at end of file diff --git a/user/themes/test/css-compiled/spectre.css b/user/themes/test/css-compiled/spectre.css new file mode 100755 index 0000000..54aaa22 --- /dev/null +++ b/user/themes/test/css-compiled/spectre.css @@ -0,0 +1,1257 @@ +/*! Spectre.css v0.5.8 | MIT License | github.com/picturepan2/spectre */ +/* Manually forked from Normalize.css */ +/* normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */ +/** 1. Change the default font family in all browsers (opinionated). 2. Correct the line height in all browsers. 3. Prevent adjustments of font size after orientation changes in IE on Windows Phone and in iOS. */ +/* Document ========================================================================== */ +html { font-family: sans-serif; /* 1 */ -ms-text-size-adjust: 100%; /* 3 */ -webkit-text-size-adjust: 100%; /* 3 */ } + +/* Sections ========================================================================== */ +/** Remove the margin in all browsers (opinionated). */ +body { margin: 0; } + +/** Add the correct display in IE 9-. */ +article, aside, footer, header, nav, section { display: block; } + +/** Correct the font size and margin on `h1` elements within `section` and `article` contexts in Chrome, Firefox, and Safari. */ +h1 { font-size: 2em; margin: 0.67em 0; } + +/* Grouping content ========================================================================== */ +/** Add the correct display in IE 9-. 1. Add the correct display in IE. */ +figcaption, figure, main { /* 1 */ display: block; } + +/** Add the correct margin in IE 8 (removed). */ +/** 1. Add the correct box sizing in Firefox. 2. Show the overflow in Edge and IE. */ +hr { box-sizing: content-box; /* 1 */ height: 0; /* 1 */ overflow: visible; /* 2 */ } + +/** 1. Correct the inheritance and scaling of font size in all browsers. (removed) 2. Correct the odd `em` font sizing in all browsers. */ +/* Text-level semantics ========================================================================== */ +/** 1. Remove the gray background on active links in IE 10. 2. Remove gaps in links underline in iOS 8+ and Safari 8+. */ +a { background-color: transparent; /* 1 */ -webkit-text-decoration-skip: objects; /* 2 */ } + +/** Remove the outline on focused links when they are also active or hovered in all browsers (opinionated). */ +a:active, a:hover { outline-width: 0; } + +/** Modify default styling of address. */ +address { font-style: normal; } + +/** 1. Remove the bottom border in Firefox 39-. 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. (removed) */ +/** Prevent the duplicate application of `bolder` by the next rule in Safari 6. */ +b, strong { font-weight: inherit; } + +/** Add the correct font weight in Chrome, Edge, and Safari. */ +b, strong { font-weight: bolder; } + +/** 1. Correct the inheritance and scaling of font size in all browsers. 2. Correct the odd `em` font sizing in all browsers. */ +code, kbd, pre, samp { font-family: "SF Mono", "Segoe UI Mono", "Roboto Mono", Menlo, Courier, monospace; /* 1 (changed) */ font-size: 1em; /* 2 */ } + +/** Add the correct font style in Android 4.3-. */ +dfn { font-style: italic; } + +/** Add the correct background and color in IE 9-. (Removed) */ +/** Add the correct font size in all browsers. */ +small { font-size: 80%; font-weight: 400; /* (added) */ } + +/** Prevent `sub` and `sup` elements from affecting the line height in all browsers. */ +sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } + +sub { bottom: -0.25em; } + +sup { top: -0.5em; } + +/* Embedded content ========================================================================== */ +/** Add the correct display in IE 9-. */ +audio, video { display: inline-block; } + +/** Add the correct display in iOS 4-7. */ +audio:not([controls]) { display: none; height: 0; } + +/** Remove the border on images inside links in IE 10-. */ +img { border-style: none; } + +/** Hide the overflow in IE. */ +svg:not(:root) { overflow: hidden; } + +/* Forms ========================================================================== */ +/** 1. Change the font styles in all browsers (opinionated). 2. Remove the margin in Firefox and Safari. */ +button, input, optgroup, select, textarea { font-family: inherit; /* 1 (changed) */ font-size: inherit; /* 1 (changed) */ line-height: inherit; /* 1 (changed) */ margin: 0; /* 2 */ } + +/** Show the overflow in IE. 1. Show the overflow in Edge. */ +button, input { /* 1 */ overflow: visible; } + +/** Remove the inheritance of text transform in Edge, Firefox, and IE. 1. Remove the inheritance of text transform in Firefox. */ +button, select { /* 1 */ text-transform: none; } + +/** 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` controls in Android 4. 2. Correct the inability to style clickable types in iOS and Safari. */ +button, html [type="button"], [type="reset"], [type="submit"] { -webkit-appearance: button; /* 2 */ } + +/** Remove the inner border and padding in Firefox. */ +button::-moz-focus-inner, [type="button"]::-moz-focus-inner, [type="reset"]::-moz-focus-inner, [type="submit"]::-moz-focus-inner { border-style: none; padding: 0; } + +/** Restore the focus styles unset by the previous rule (removed). */ +/** Change the border, margin, and padding in all browsers (opinionated) (changed). */ +fieldset { border: 0; margin: 0; padding: 0; } + +/** 1. Correct the text wrapping in Edge and IE. 2. Correct the color inheritance from `fieldset` elements in IE. 3. Remove the padding so developers are not caught out when they zero out `fieldset` elements in all browsers. */ +legend { box-sizing: border-box; /* 1 */ color: inherit; /* 2 */ display: table; /* 1 */ max-width: 100%; /* 1 */ padding: 0; /* 3 */ white-space: normal; /* 1 */ } + +/** 1. Add the correct display in IE 9-. 2. Add the correct vertical alignment in Chrome, Firefox, and Opera. */ +progress { display: inline-block; /* 1 */ vertical-align: baseline; /* 2 */ } + +/** Remove the default vertical scrollbar in IE. */ +textarea { overflow: auto; } + +/** 1. Add the correct box sizing in IE 10-. 2. Remove the padding in IE 10-. */ +[type="checkbox"], [type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ } + +/** Correct the cursor style of increment and decrement buttons in Chrome. */ +[type="number"]::-webkit-inner-spin-button, [type="number"]::-webkit-outer-spin-button { height: auto; } + +/** 1. Correct the odd appearance in Chrome and Safari. 2. Correct the outline style in Safari. */ +[type="search"] { -webkit-appearance: textfield; /* 1 */ outline-offset: -2px; /* 2 */ } + +/** Remove the inner padding and cancel buttons in Chrome and Safari on macOS. */ +[type="search"]::-webkit-search-cancel-button, [type="search"]::-webkit-search-decoration { -webkit-appearance: none; } + +/** 1. Correct the inability to style clickable types in iOS and Safari. 2. Change font properties to `inherit` in Safari. */ +::-webkit-file-upload-button { -webkit-appearance: button; /* 1 */ font: inherit; /* 2 */ } + +/* Interactive ========================================================================== */ +/* Add the correct display in IE 9-. 1. Add the correct display in Edge, IE, and Firefox. */ +details, menu { display: block; } + +/* Add the correct display in all browsers. */ +summary { display: list-item; outline: none; } + +/* Scripting ========================================================================== */ +/** Add the correct display in IE 9-. */ +canvas { display: inline-block; } + +/** Add the correct display in IE. */ +template { display: none; } + +/* Hidden ========================================================================== */ +/** Add the correct display in IE 10-. */ +[hidden] { display: none; } + +*, *::before, *::after { box-sizing: inherit; } + +html { box-sizing: border-box; font-size: 20px; line-height: 1.5; -webkit-tap-highlight-color: transparent; } + +body { background: #fff; color: #50596c; font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif; font-size: 0.8rem; overflow-x: hidden; text-rendering: optimizeLegibility; } + +a { color: #3085EE; outline: none; text-decoration: none; } + +a:focus { box-shadow: 0 0 0 0.1rem rgba(48, 133, 238, 0.2); } + +a:focus, a:hover, a:active, a.active { color: #126bd9; text-decoration: underline; } + +a:visited { color: #5fa1f2; } + +h1, h2, h3, h4, h5, h6 { color: inherit; font-weight: 500; line-height: 1.2; margin-bottom: .5em; margin-top: 0; } + +.h1, .h2, .h3, .h4, .h5, .h6 { font-weight: 500; } + +h1, .h1 { font-size: 2rem; } + +h2, .h2 { font-size: 1.6rem; } + +h3, .h3 { font-size: 1.4rem; } + +h4, .h4 { font-size: 1.2rem; } + +h5, .h5 { font-size: 1rem; } + +h6, .h6 { font-size: .8rem; } + +p { margin: 0 0 1.2rem; } + +a, ins, u { -webkit-text-decoration-skip: ink edges; text-decoration-skip: ink edges; } + +abbr[title] { border-bottom: 0.05rem dotted; cursor: help; text-decoration: none; } + +kbd { border-radius: 0.1rem; line-height: 1.25; padding: .1rem .2rem; background: #454d5d; color: #fff; font-size: 0.7rem; } + +mark { background: #ffe9b3; color: #50596c; border-bottom: 0.05rem solid #ffd367; border-radius: 0.1rem; padding: 0.05rem 0.1rem 0; } + +blockquote { border-left: 0.1rem solid #e7e9ed; margin-left: 0; padding: 0.4rem 0.8rem; } + +blockquote p:last-child { margin-bottom: 0; } + +ul, ol { margin: 0.8rem 0 0.8rem 0.8rem; padding: 0; } + +ul ul, ul ol, ol ul, ol ol { margin: 0.8rem 0 0.8rem 0.8rem; } + +ul li, ol li { margin-top: 0.4rem; } + +ul { list-style: disc inside; } + +ul ul { list-style-type: circle; } + +ol { list-style: decimal inside; } + +ol ol { list-style-type: lower-alpha; } + +dl dt { font-weight: bold; } + +dl dd { margin: 0.4rem 0 0.8rem 0; } + +html:lang(zh), html:lang(zh-Hans), .lang-zh, .lang-zh-hans { font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", sans-serif; } + +html:lang(zh-Hant), .lang-zh-hant { font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang TC", "Hiragino Sans CNS", "Microsoft JhengHei", "Helvetica Neue", sans-serif; } + +html:lang(ja), .lang-ja { font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Hiragino Sans", "Hiragino Kaku Gothic Pro", "Yu Gothic", YuGothic, Meiryo, "Helvetica Neue", sans-serif; } + +html:lang(ko), .lang-ko { font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Malgun Gothic", "Helvetica Neue", sans-serif; } + +:lang(zh) ins, :lang(zh) u, :lang(ja) ins, :lang(ja) u, .lang-cjk ins, .lang-cjk u { border-bottom: 0.05rem solid; text-decoration: none; } + +:lang(zh) del + del, :lang(zh) del + s, :lang(zh) ins + ins, :lang(zh) ins + u, :lang(zh) s + del, :lang(zh) s + s, :lang(zh) u + ins, :lang(zh) u + u, :lang(ja) del + del, :lang(ja) del + s, :lang(ja) ins + ins, :lang(ja) ins + u, :lang(ja) s + del, :lang(ja) s + s, :lang(ja) u + ins, :lang(ja) u + u, .lang-cjk del + del, .lang-cjk del + s, .lang-cjk ins + ins, .lang-cjk ins + u, .lang-cjk s + del, .lang-cjk s + s, .lang-cjk u + ins, .lang-cjk u + u { margin-left: .125em; } + +.table { border-collapse: collapse; border-spacing: 0; width: 100%; text-align: left; } + +.table.table-striped tbody tr:nth-of-type(odd) { background: #f8f9fa; } + +.table tbody tr.active, .table.table-striped tbody tr.active { background: #f0f1f4; } + +.table.table-hover tbody tr:hover { background: #f0f1f4; } + +.table.table-scroll { display: block; overflow-x: auto; padding-bottom: .75rem; white-space: nowrap; } + +.table td, .table th { border-bottom: 0.05rem solid #e7e9ed; padding: 0.6rem 0.4rem; } + +.table th { border-bottom-width: 0.1rem; } + +.btn, .button { -webkit-appearance: none; -moz-appearance: none; appearance: none; background: #fff; border: 0.05rem solid #3085EE; border-radius: 0.1rem; color: #3085EE; cursor: pointer; display: inline-block; font-size: 0.8rem; height: 1.8rem; line-height: 1.2rem; outline: none; padding: 0.25rem 0.4rem; text-align: center; text-decoration: none; transition: background .2s, border .2s, box-shadow .2s, color .2s; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; vertical-align: middle; white-space: nowrap; } + +.btn:focus, .button:focus { box-shadow: 0 0 0 0.1rem rgba(48, 133, 238, 0.2); } + +.btn:focus, .button:focus, .btn:hover, .button:hover { background: #e1edfd; border-color: #227ded; text-decoration: none; } + +.btn:active, .button:active, .btn.active, .active.button { background: #227ded; border-color: #1370e3; color: #fff; text-decoration: none; } + +.btn:active.loading::after, .button:active.loading::after, .btn.active.loading::after, .active.loading.button::after { border-bottom-color: #fff; border-left-color: #fff; } + +.btn[disabled], .button[disabled], .btn:disabled, .button:disabled, .btn.disabled, .disabled.button { cursor: default; opacity: .5; pointer-events: none; } + +.btn.btn-primary, .btn-primary.button { background: #3085EE; border-color: #227ded; color: #fff; } + +.btn.btn-primary:focus, .btn-primary.button:focus, .btn.btn-primary:hover, .btn-primary.button:hover { background: #1877ec; border-color: #1370e3; color: #fff; } + +.btn.btn-primary:active, .btn-primary.button:active, .btn.btn-primary.active, .btn-primary.active.button { background: #1372e7; border-color: #126bd9; color: #fff; } + +.btn.btn-primary.loading::after, .btn-primary.loading.button::after { border-bottom-color: #fff; border-left-color: #fff; } + +.btn.btn-success, .btn-success.button { background: #32b643; border-color: #2faa3f; color: #fff; } + +.btn.btn-success:focus, .btn-success.button:focus { box-shadow: 0 0 0 0.1rem rgba(50, 182, 67, 0.2); } + +.btn.btn-success:focus, .btn-success.button:focus, .btn.btn-success:hover, .btn-success.button:hover { background: #30ae40; border-color: #2da23c; color: #fff; } + +.btn.btn-success:active, .btn-success.button:active, .btn.btn-success.active, .btn-success.active.button { background: #2a9a39; border-color: #278e34; color: #fff; } + +.btn.btn-success.loading::after, .btn-success.loading.button::after { border-bottom-color: #fff; border-left-color: #fff; } + +.btn.btn-error, .btn-error.button { background: #e85600; border-color: #d95000; color: #fff; } + +.btn.btn-error:focus, .btn-error.button:focus { box-shadow: 0 0 0 0.1rem rgba(232, 86, 0, 0.2); } + +.btn.btn-error:focus, .btn-error.button:focus, .btn.btn-error:hover, .btn-error.button:hover { background: #de5200; border-color: #cf4d00; color: #fff; } + +.btn.btn-error:active, .btn-error.button:active, .btn.btn-error.active, .btn-error.active.button { background: #c44900; border-color: #b54300; color: #fff; } + +.btn.btn-error.loading::after, .btn-error.loading.button::after { border-bottom-color: #fff; border-left-color: #fff; } + +.btn.btn-link, .btn-link.button { background: transparent; border-color: transparent; color: #3085EE; } + +.btn.btn-link:focus, .btn-link.button:focus, .btn.btn-link:hover, .btn-link.button:hover, .btn.btn-link:active, .btn-link.button:active, .btn.btn-link.active, .btn-link.active.button { color: #126bd9; } + +.btn.btn-sm, .btn-sm.button { font-size: 0.7rem; height: 1.4rem; padding: 0.05rem 0.3rem; } + +.btn.btn-lg, .btn-lg.button { font-size: 0.9rem; height: 2rem; padding: 0.35rem 0.6rem; } + +.btn.btn-block, .btn-block.button { display: block; width: 100%; } + +.btn.btn-action, .btn-action.button { width: 1.8rem; padding-left: 0; padding-right: 0; } + +.btn.btn-action.btn-sm, .btn-action.btn-sm.button { width: 1.4rem; } + +.btn.btn-action.btn-lg, .btn-action.btn-lg.button { width: 2rem; } + +.btn.btn-clear, .btn-clear.button { background: transparent; border: 0; color: currentColor; height: 1rem; line-height: 0.8rem; margin-left: 0.2rem; margin-right: -2px; opacity: 1; padding: 0.1rem; text-decoration: none; width: 1rem; } + +.btn.btn-clear:focus, .btn-clear.button:focus, .btn.btn-clear:hover, .btn-clear.button:hover { background: rgba(248, 249, 250, 0.5); opacity: .95; } + +.btn.btn-clear::before, .btn-clear.button::before { content: "\2715"; } + +.btn-group { display: -ms-inline-flexbox; display: inline-flex; -ms-flex-wrap: wrap; flex-wrap: wrap; } + +.btn-group .btn, .btn-group .button { -ms-flex: 1 0 auto; flex: 1 0 auto; } + +.btn-group .btn:first-child:not(:last-child), .btn-group .button:first-child:not(:last-child) { border-bottom-right-radius: 0; border-top-right-radius: 0; } + +.btn-group .btn:not(:first-child):not(:last-child), .btn-group .button:not(:first-child):not(:last-child) { border-radius: 0; margin-left: -0.05rem; } + +.btn-group .btn:last-child:not(:first-child), .btn-group .button:last-child:not(:first-child) { border-bottom-left-radius: 0; border-top-left-radius: 0; margin-left: -0.05rem; } + +.btn-group .btn:focus, .btn-group .button:focus, .btn-group .btn:hover, .btn-group .button:hover, .btn-group .btn:active, .btn-group .button:active, .btn-group .btn.active, .btn-group .active.button { z-index: 1; } + +.btn-group.btn-group-block { display: -ms-flexbox; display: flex; } + +.btn-group.btn-group-block .btn, .btn-group.btn-group-block .button { -ms-flex: 1 0 0px; flex: 1 0 0; } + +.form-group:not(:last-child) { margin-bottom: 0.4rem; } + +fieldset { margin-bottom: 0.8rem; } + +legend { font-size: 0.9rem; font-weight: 500; margin-bottom: 0.8rem; } + +.form-label { display: block; line-height: 1.2rem; padding: 0.3rem 0; } + +.form-label.label-sm { font-size: 0.7rem; padding: 0.1rem 0; } + +.form-label.label-lg { font-size: 0.9rem; padding: 0.4rem 0; } + +.form-input, .search-input, [data-grav-field="array"] input, [data-grav-field="array"] textarea { -webkit-appearance: none; -moz-appearance: none; appearance: none; background: #fff; background-image: none; border: 0.05rem solid #caced7; border-radius: 0.1rem; color: #50596c; display: block; font-size: 0.8rem; height: 1.8rem; line-height: 1.2rem; max-width: 100%; outline: none; padding: 0.25rem 0.4rem; position: relative; transition: background .2s, border .2s, box-shadow .2s, color .2s; width: 100%; } + +.form-input:focus, .search-input:focus, [data-grav-field="array"] input:focus, [data-grav-field="array"] textarea:focus { box-shadow: 0 0 0 0.1rem rgba(48, 133, 238, 0.2); border-color: #3085EE; } + +.form-input::-webkit-input-placeholder, .search-input::-webkit-input-placeholder, [data-grav-field="array"] input::-webkit-input-placeholder, [data-grav-field="array"] textarea::-webkit-input-placeholder { color: #acb3c2; } + +.form-input:-ms-input-placeholder, .search-input:-ms-input-placeholder, [data-grav-field="array"] input:-ms-input-placeholder, [data-grav-field="array"] textarea:-ms-input-placeholder { color: #acb3c2; } + +.form-input::-ms-input-placeholder, .search-input::-ms-input-placeholder, [data-grav-field="array"] input::-ms-input-placeholder, [data-grav-field="array"] textarea::-ms-input-placeholder { color: #acb3c2; } + +.form-input::placeholder, .search-input::placeholder, [data-grav-field="array"] input::placeholder, [data-grav-field="array"] textarea::placeholder { color: #acb3c2; } + +.form-input.input-sm, .input-sm.search-input, [data-grav-field="array"] input.input-sm, [data-grav-field="array"] textarea.input-sm { font-size: 0.7rem; height: 1.4rem; padding: 0.05rem 0.3rem; } + +.form-input.input-lg, .input-lg.search-input, [data-grav-field="array"] input.input-lg, [data-grav-field="array"] textarea.input-lg { font-size: 0.9rem; height: 2rem; padding: 0.35rem 0.6rem; } + +.form-input.input-inline, .input-inline.search-input, [data-grav-field="array"] input.input-inline, [data-grav-field="array"] textarea.input-inline { display: inline-block; vertical-align: middle; width: auto; } + +.form-input[type="file"], .search-input[type="file"], [data-grav-field="array"] input[type="file"], [data-grav-field="array"] textarea[type="file"] { height: auto; } + +textarea.form-input, textarea.search-input, [data-grav-field="array"] textarea, textarea.form-input.input-lg, textarea.input-lg.search-input, [data-grav-field="array"] textarea.input-lg, textarea.form-input.input-sm, textarea.input-sm.search-input, [data-grav-field="array"] textarea.input-sm { height: auto; } + +.form-input-hint { color: #acb3c2; font-size: 0.7rem; margin-top: 0.2rem; } + +.has-success .form-input-hint, .is-success + .form-input-hint { color: #32b643; } + +.has-error .form-input-hint, .is-error + .form-input-hint { color: #e85600; } + +.form-select { -webkit-appearance: none; -moz-appearance: none; appearance: none; border: 0.05rem solid #caced7; border-radius: 0.1rem; color: inherit; font-size: 0.8rem; height: 1.8rem; line-height: 1.2rem; outline: none; padding: 0.25rem 0.4rem; vertical-align: middle; width: 100%; background: #fff; } + +.form-select:focus { box-shadow: 0 0 0 0.1rem rgba(48, 133, 238, 0.2); border-color: #3085EE; } + +.form-select::-ms-expand { display: none; } + +.form-select.select-sm { font-size: 0.7rem; height: 1.4rem; padding: 0.05rem 1.1rem 0.05rem 0.3rem; } + +.form-select.select-lg { font-size: 0.9rem; height: 2rem; padding: 0.35rem 1.4rem 0.35rem 0.6rem; } + +.form-select[size], .form-select[multiple] { height: auto; padding: 0.25rem 0.4rem; } + +.form-select[size] option, .form-select[multiple] option { padding: 0.1rem 0.2rem; } + +.form-select:not([multiple]):not([size]) { background: #fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right 0.35rem center/0.4rem 0.5rem; padding-right: 1.2rem; } + +.has-icon-left, .has-icon-right { position: relative; } + +.has-icon-left .form-icon, .has-icon-right .form-icon { height: 0.8rem; margin: 0 0.25rem; position: absolute; top: 50%; transform: translateY(-50%); width: 0.8rem; z-index: 2; } + +.has-icon-left .form-icon { left: 0.05rem; } + +.has-icon-left .form-input, .has-icon-left .search-input, .has-icon-left [data-grav-field="array"] input, [data-grav-field="array"] .has-icon-left input, .has-icon-left [data-grav-field="array"] textarea, [data-grav-field="array"] .has-icon-left textarea { padding-left: 1.3rem; } + +.has-icon-right .form-icon { right: 0.05rem; } + +.has-icon-right .form-input, .has-icon-right .search-input, .has-icon-right [data-grav-field="array"] input, [data-grav-field="array"] .has-icon-right input, .has-icon-right [data-grav-field="array"] textarea, [data-grav-field="array"] .has-icon-right textarea { padding-right: 1.3rem; } + +.form-checkbox, .form-radio, .form-switch { display: block; line-height: 1.2rem; margin: 0.2rem 0; min-height: 1.4rem; padding: 0.1rem 0.4rem 0.1rem 1.2rem; position: relative; } + +.form-checkbox input, .form-radio input, .form-switch input { clip: rect(0, 0, 0, 0); height: 1px; margin: -1px; overflow: hidden; position: absolute; width: 1px; } + +.form-checkbox input:focus + .form-icon, .form-radio input:focus + .form-icon, .form-switch input:focus + .form-icon { box-shadow: 0 0 0 0.1rem rgba(48, 133, 238, 0.2); border-color: #3085EE; } + +.form-checkbox input:checked + .form-icon, .form-radio input:checked + .form-icon, .form-switch input:checked + .form-icon { background: #3085EE; border-color: #3085EE; } + +.form-checkbox .form-icon, .form-radio .form-icon, .form-switch .form-icon { border: 0.05rem solid #caced7; cursor: pointer; display: inline-block; position: absolute; transition: background .2s, border .2s, box-shadow .2s, color .2s; } + +.form-checkbox.input-sm, .form-radio.input-sm, .form-switch.input-sm { font-size: 0.7rem; margin: 0; } + +.form-checkbox.input-lg, .form-radio.input-lg, .form-switch.input-lg { font-size: 0.9rem; margin: 0.3rem 0; } + +.form-checkbox .form-icon, .form-radio .form-icon { background: #fff; height: 0.8rem; left: 0; top: 0.3rem; width: 0.8rem; } + +.form-checkbox input:active + .form-icon, .form-radio input:active + .form-icon { background: #f0f1f4; } + +.form-checkbox .form-icon { border-radius: 0.1rem; } + +.form-checkbox input:checked + .form-icon::before { background-clip: padding-box; border: 0.1rem solid #fff; border-left-width: 0; border-top-width: 0; content: ""; height: 9px; left: 50%; margin-left: -3px; margin-top: -6px; position: absolute; top: 50%; transform: rotate(45deg); width: 6px; } + +.form-checkbox input:indeterminate + .form-icon { background: #3085EE; border-color: #3085EE; } + +.form-checkbox input:indeterminate + .form-icon::before { background: #fff; content: ""; height: 2px; left: 50%; margin-left: -5px; margin-top: -1px; position: absolute; top: 50%; width: 10px; } + +.form-radio .form-icon { border-radius: 50%; } + +.form-radio input:checked + .form-icon::before { background: #fff; border-radius: 50%; content: ""; height: 6px; left: 50%; position: absolute; top: 50%; transform: translate(-50%, -50%); width: 6px; } + +.form-switch { padding-left: 2rem; } + +.form-switch .form-icon { background: #acb3c2; background-clip: padding-box; border-radius: 0.45rem; height: 0.9rem; left: 0; top: 0.25rem; width: 1.6rem; } + +.form-switch .form-icon::before { background: #fff; border-radius: 50%; content: ""; display: block; height: 0.8rem; left: 0; position: absolute; top: 0; transition: background .2s, border .2s, box-shadow .2s, color .2s, left .2s; width: 0.8rem; } + +.form-switch input:checked + .form-icon::before { left: 14px; } + +.form-switch input:active + .form-icon::before { background: #f8f9fa; } + +.input-group { display: -ms-flexbox; display: flex; } + +.input-group .input-group-addon { background: #f8f9fa; border: 0.05rem solid #caced7; border-radius: 0.1rem; line-height: 1.2rem; padding: 0.25rem 0.4rem; white-space: nowrap; } + +.input-group .input-group-addon.addon-sm { font-size: 0.7rem; padding: 0.05rem 0.3rem; } + +.input-group .input-group-addon.addon-lg { font-size: 0.9rem; padding: 0.35rem 0.6rem; } + +.input-group .form-input, .input-group .search-input, .input-group [data-grav-field="array"] input, [data-grav-field="array"] .input-group input, .input-group [data-grav-field="array"] textarea, [data-grav-field="array"] .input-group textarea, .input-group .form-select { -ms-flex: 1 1 auto; flex: 1 1 auto; width: 1%; } + +.input-group .input-group-btn { z-index: 1; } + +.input-group .form-input:first-child:not(:last-child), .input-group .search-input:first-child:not(:last-child), .input-group [data-grav-field="array"] input:first-child:not(:last-child), [data-grav-field="array"] .input-group input:first-child:not(:last-child), .input-group [data-grav-field="array"] textarea:first-child:not(:last-child), [data-grav-field="array"] .input-group textarea:first-child:not(:last-child), .input-group .form-select:first-child:not(:last-child), .input-group .input-group-addon:first-child:not(:last-child), .input-group .input-group-btn:first-child:not(:last-child) { border-bottom-right-radius: 0; border-top-right-radius: 0; } + +.input-group .form-input:not(:first-child):not(:last-child), .input-group .search-input:not(:first-child):not(:last-child), .input-group [data-grav-field="array"] input:not(:first-child):not(:last-child), [data-grav-field="array"] .input-group input:not(:first-child):not(:last-child), .input-group [data-grav-field="array"] textarea:not(:first-child):not(:last-child), [data-grav-field="array"] .input-group textarea:not(:first-child):not(:last-child), .input-group .form-select:not(:first-child):not(:last-child), .input-group .input-group-addon:not(:first-child):not(:last-child), .input-group .input-group-btn:not(:first-child):not(:last-child) { border-radius: 0; margin-left: -0.05rem; } + +.input-group .form-input:last-child:not(:first-child), .input-group .search-input:last-child:not(:first-child), .input-group [data-grav-field="array"] input:last-child:not(:first-child), [data-grav-field="array"] .input-group input:last-child:not(:first-child), .input-group [data-grav-field="array"] textarea:last-child:not(:first-child), [data-grav-field="array"] .input-group textarea:last-child:not(:first-child), .input-group .form-select:last-child:not(:first-child), .input-group .input-group-addon:last-child:not(:first-child), .input-group .input-group-btn:last-child:not(:first-child) { border-bottom-left-radius: 0; border-top-left-radius: 0; margin-left: -0.05rem; } + +.input-group .form-input:focus, .input-group .search-input:focus, .input-group [data-grav-field="array"] input:focus, [data-grav-field="array"] .input-group input:focus, .input-group [data-grav-field="array"] textarea:focus, [data-grav-field="array"] .input-group textarea:focus, .input-group .form-select:focus, .input-group .input-group-addon:focus, .input-group .input-group-btn:focus { z-index: 2; } + +.input-group .form-select { width: auto; } + +.input-group.input-inline { display: -ms-inline-flexbox; display: inline-flex; } + +.has-success .form-input, .has-success .search-input, .has-success [data-grav-field="array"] input, [data-grav-field="array"] .has-success input, .has-success [data-grav-field="array"] textarea, [data-grav-field="array"] .has-success textarea, .form-input.is-success, .is-success.search-input, [data-grav-field="array"] input.is-success, [data-grav-field="array"] textarea.is-success, .has-success .form-select, .form-select.is-success { background: #f9fdfa; border-color: #32b643; } + +.has-success .form-input:focus, .has-success .search-input:focus, .has-success [data-grav-field="array"] input:focus, [data-grav-field="array"] .has-success input:focus, .has-success [data-grav-field="array"] textarea:focus, [data-grav-field="array"] .has-success textarea:focus, .form-input.is-success:focus, .is-success.search-input:focus, [data-grav-field="array"] input.is-success:focus, [data-grav-field="array"] textarea.is-success:focus, .has-success .form-select:focus, .form-select.is-success:focus { box-shadow: 0 0 0 0.1rem rgba(50, 182, 67, 0.2); } + +.has-error .form-input, .has-error .search-input, .has-error [data-grav-field="array"] input, [data-grav-field="array"] .has-error input, .has-error [data-grav-field="array"] textarea, [data-grav-field="array"] .has-error textarea, .form-input.is-error, .is-error.search-input, [data-grav-field="array"] input.is-error, [data-grav-field="array"] textarea.is-error, .has-error .form-select, .form-select.is-error { background: #fffaf7; border-color: #e85600; } + +.has-error .form-input:focus, .has-error .search-input:focus, .has-error [data-grav-field="array"] input:focus, [data-grav-field="array"] .has-error input:focus, .has-error [data-grav-field="array"] textarea:focus, [data-grav-field="array"] .has-error textarea:focus, .form-input.is-error:focus, .is-error.search-input:focus, [data-grav-field="array"] input.is-error:focus, [data-grav-field="array"] textarea.is-error:focus, .has-error .form-select:focus, .form-select.is-error:focus { box-shadow: 0 0 0 0.1rem rgba(232, 86, 0, 0.2); } + +.has-error .form-checkbox .form-icon, .form-checkbox.is-error .form-icon, .has-error .form-radio .form-icon, .form-radio.is-error .form-icon, .has-error .form-switch .form-icon, .form-switch.is-error .form-icon { border-color: #e85600; } + +.has-error .form-checkbox input:checked + .form-icon, .form-checkbox.is-error input:checked + .form-icon, .has-error .form-radio input:checked + .form-icon, .form-radio.is-error input:checked + .form-icon, .has-error .form-switch input:checked + .form-icon, .form-switch.is-error input:checked + .form-icon { background: #e85600; border-color: #e85600; } + +.has-error .form-checkbox input:focus + .form-icon, .form-checkbox.is-error input:focus + .form-icon, .has-error .form-radio input:focus + .form-icon, .form-radio.is-error input:focus + .form-icon, .has-error .form-switch input:focus + .form-icon, .form-switch.is-error input:focus + .form-icon { box-shadow: 0 0 0 0.1rem rgba(232, 86, 0, 0.2); border-color: #e85600; } + +.has-error .form-checkbox input:indeterminate + .form-icon, .form-checkbox.is-error input:indeterminate + .form-icon { background: #e85600; border-color: #e85600; } + +.form-input:not(:placeholder-shown):invalid, .search-input:not(:placeholder-shown):invalid, [data-grav-field="array"] input:not(:placeholder-shown):invalid, [data-grav-field="array"] textarea:not(:placeholder-shown):invalid { border-color: #e85600; } + +.form-input:not(:placeholder-shown):invalid:focus, .search-input:not(:placeholder-shown):invalid:focus, [data-grav-field="array"] input:not(:placeholder-shown):invalid:focus, [data-grav-field="array"] textarea:not(:placeholder-shown):invalid:focus { box-shadow: 0 0 0 0.1rem rgba(232, 86, 0, 0.2); background: #fffaf7; } + +.form-input:not(:placeholder-shown):invalid + .form-input-hint, .search-input:not(:placeholder-shown):invalid + .form-input-hint, [data-grav-field="array"] input:not(:placeholder-shown):invalid + .form-input-hint, [data-grav-field="array"] textarea:not(:placeholder-shown):invalid + .form-input-hint { color: #e85600; } + +.form-input:disabled, .search-input:disabled, [data-grav-field="array"] input:disabled, [data-grav-field="array"] textarea:disabled, .form-input.disabled, .disabled.search-input, [data-grav-field="array"] input.disabled, [data-grav-field="array"] textarea.disabled, .form-select:disabled, .form-select.disabled { background-color: #f0f1f4; cursor: not-allowed; opacity: .5; } + +.form-input[readonly], .search-input[readonly], [data-grav-field="array"] input[readonly], [data-grav-field="array"] textarea[readonly] { background-color: #f8f9fa; } + +input:disabled + .form-icon, input.disabled + .form-icon { background: #f0f1f4; cursor: not-allowed; opacity: .5; } + +.form-switch input:disabled + .form-icon::before, .form-switch input.disabled + .form-icon::before { background: #fff; } + +.form-horizontal { padding: 0.4rem 0; } + +.form-horizontal .form-group { display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; } + +.form-inline { display: inline-block; } + +.label { border-radius: 0.1rem; line-height: 1.25; padding: .1rem .2rem; background: #f0f1f4; color: #5b657a; display: inline-block; } + +.label.label-rounded { border-radius: 5rem; padding-left: .4rem; padding-right: .4rem; } + +.label.label-primary { background: #3085EE; color: #fff; } + +.label.label-secondary { background: #e1edfd; color: #3085EE; } + +.label.label-success { background: #32b643; color: #fff; } + +.label.label-warning { background: #ffb700; color: #fff; } + +.label.label-error { background: #e85600; color: #fff; } + +code { border-radius: 0.1rem; line-height: 1.25; padding: .1rem .2rem; background: #fcf2f2; color: #d73e48; font-size: 85%; } + +.code { border-radius: 0.1rem; color: #50596c; position: relative; } + +.code::before { color: #acb3c2; content: attr(data-lang); font-size: 0.7rem; position: absolute; right: 0.4rem; top: 0.1rem; } + +.code code { background: #f8f9fa; color: inherit; display: block; line-height: 1.5; overflow-x: auto; padding: 1rem; width: 100%; } + +.img-responsive { display: block; height: auto; max-width: 100%; } + +.img-fit-cover { object-fit: cover; } + +.img-fit-contain { object-fit: contain; } + +.video-responsive { display: block; overflow: hidden; padding: 0; position: relative; width: 100%; } + +.video-responsive::before { content: ""; display: block; padding-bottom: 56.25%; } + +.video-responsive iframe, .video-responsive object, .video-responsive embed { border: 0; bottom: 0; height: 100%; left: 0; position: absolute; right: 0; top: 0; width: 100%; } + +video.video-responsive { height: auto; max-width: 100%; } + +video.video-responsive::before { content: none; } + +.video-responsive-4-3::before { padding-bottom: 75%; } + +.video-responsive-1-1::before { padding-bottom: 100%; } + +.figure { margin: 0 0 0.4rem 0; } + +.figure .figure-caption { color: #667189; margin-top: 0.4rem; } + +.container { margin-left: auto; margin-right: auto; padding-left: 0.4rem; padding-right: 0.4rem; width: 100%; } + +.container.grid-xl { max-width: 1296px; } + +.container.grid-lg { max-width: 976px; } + +.container.grid-md { max-width: 856px; } + +.container.grid-sm { max-width: 616px; } + +.container.grid-xs { max-width: 496px; } + +.show-xs, .show-sm, .show-md, .show-lg, .show-xl { display: none !important; } + +.columns { display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; margin-left: -0.4rem; margin-right: -0.4rem; } + +.columns.col-gapless { margin-left: 0; margin-right: 0; } + +.columns.col-gapless > .column { padding-left: 0; padding-right: 0; } + +.columns.col-oneline { -ms-flex-wrap: nowrap; flex-wrap: nowrap; overflow-x: auto; } + +.column { -ms-flex: 1; flex: 1; max-width: 100%; padding-left: 0.4rem; padding-right: 0.4rem; } + +.column.col-12, .column.col-11, .column.col-10, .column.col-9, .column.col-8, .column.col-7, .column.col-6, .column.col-5, .column.col-4, .column.col-3, .column.col-2, .column.col-1, .column.col-auto { -ms-flex: none; flex: none; } + +.col-12 { width: 100%; } + +.col-11 { width: 91.66666667%; } + +.col-10 { width: 83.33333333%; } + +.col-9 { width: 75%; } + +.col-8 { width: 66.66666667%; } + +.col-7 { width: 58.33333333%; } + +.col-6 { width: 50%; } + +.col-5 { width: 41.66666667%; } + +.col-4 { width: 33.33333333%; } + +.col-3 { width: 25%; } + +.col-2 { width: 16.66666667%; } + +.col-1 { width: 8.33333333%; } + +.col-auto { -ms-flex: 0 0 auto; flex: 0 0 auto; max-width: none; width: auto; } + +.col-mx-auto { margin-left: auto; margin-right: auto; } + +.col-ml-auto { margin-left: auto; } + +.col-mr-auto { margin-right: auto; } + +@media (max-width: 1280px) { .col-xl-12, .col-xl-11, .col-xl-10, .col-xl-9, .col-xl-8, .col-xl-7, .col-xl-6, .col-xl-5, .col-xl-4, .col-xl-3, .col-xl-2, .col-xl-1, .col-xl-auto { -ms-flex: none; flex: none; } + .col-xl-12 { width: 100%; } + .col-xl-11 { width: 91.66666667%; } + .col-xl-10 { width: 83.33333333%; } + .col-xl-9 { width: 75%; } + .col-xl-8 { width: 66.66666667%; } + .col-xl-7 { width: 58.33333333%; } + .col-xl-6 { width: 50%; } + .col-xl-5 { width: 41.66666667%; } + .col-xl-4 { width: 33.33333333%; } + .col-xl-3 { width: 25%; } + .col-xl-2 { width: 16.66666667%; } + .col-xl-1 { width: 8.33333333%; } + .col-xl-auto { width: auto; } + .hide-xl { display: none !important; } + .show-xl { display: block !important; } } + +@media (max-width: 960px) { .col-lg-12, .col-lg-11, .col-lg-10, .col-lg-9, .col-lg-8, .col-lg-7, .col-lg-6, .col-lg-5, .col-lg-4, .col-lg-3, .col-lg-2, .col-lg-1, .col-lg-auto { -ms-flex: none; flex: none; } + .col-lg-12 { width: 100%; } + .col-lg-11 { width: 91.66666667%; } + .col-lg-10 { width: 83.33333333%; } + .col-lg-9 { width: 75%; } + .col-lg-8 { width: 66.66666667%; } + .col-lg-7 { width: 58.33333333%; } + .col-lg-6 { width: 50%; } + .col-lg-5 { width: 41.66666667%; } + .col-lg-4 { width: 33.33333333%; } + .col-lg-3 { width: 25%; } + .col-lg-2 { width: 16.66666667%; } + .col-lg-1 { width: 8.33333333%; } + .col-lg-auto { width: auto; } + .hide-lg { display: none !important; } + .show-lg { display: block !important; } } + +@media (max-width: 840px) { .col-md-12, .col-md-11, .col-md-10, .col-md-9, .col-md-8, .col-md-7, .col-md-6, .col-md-5, .col-md-4, .col-md-3, .col-md-2, .col-md-1, .col-md-auto { -ms-flex: none; flex: none; } + .col-md-12 { width: 100%; } + .col-md-11 { width: 91.66666667%; } + .col-md-10 { width: 83.33333333%; } + .col-md-9 { width: 75%; } + .col-md-8 { width: 66.66666667%; } + .col-md-7 { width: 58.33333333%; } + .col-md-6 { width: 50%; } + .col-md-5 { width: 41.66666667%; } + .col-md-4 { width: 33.33333333%; } + .col-md-3 { width: 25%; } + .col-md-2 { width: 16.66666667%; } + .col-md-1 { width: 8.33333333%; } + .col-md-auto { width: auto; } + .hide-md { display: none !important; } + .show-md { display: block !important; } } + +@media (max-width: 600px) { .col-sm-12, .col-sm-11, .col-sm-10, .col-sm-9, .col-sm-8, .col-sm-7, .col-sm-6, .col-sm-5, .col-sm-4, .col-sm-3, .col-sm-2, .col-sm-1, .col-sm-auto { -ms-flex: none; flex: none; } + .col-sm-12 { width: 100%; } + .col-sm-11 { width: 91.66666667%; } + .col-sm-10 { width: 83.33333333%; } + .col-sm-9 { width: 75%; } + .col-sm-8 { width: 66.66666667%; } + .col-sm-7 { width: 58.33333333%; } + .col-sm-6 { width: 50%; } + .col-sm-5 { width: 41.66666667%; } + .col-sm-4 { width: 33.33333333%; } + .col-sm-3 { width: 25%; } + .col-sm-2 { width: 16.66666667%; } + .col-sm-1 { width: 8.33333333%; } + .col-sm-auto { width: auto; } + .hide-sm { display: none !important; } + .show-sm { display: block !important; } } + +@media (max-width: 480px) { .col-xs-12, .col-xs-11, .col-xs-10, .col-xs-9, .col-xs-8, .col-xs-7, .col-xs-6, .col-xs-5, .col-xs-4, .col-xs-3, .col-xs-2, .col-xs-1, .col-xs-auto { -ms-flex: none; flex: none; } + .col-xs-12 { width: 100%; } + .col-xs-11 { width: 91.66666667%; } + .col-xs-10 { width: 83.33333333%; } + .col-xs-9 { width: 75%; } + .col-xs-8 { width: 66.66666667%; } + .col-xs-7 { width: 58.33333333%; } + .col-xs-6 { width: 50%; } + .col-xs-5 { width: 41.66666667%; } + .col-xs-4 { width: 33.33333333%; } + .col-xs-3 { width: 25%; } + .col-xs-2 { width: 16.66666667%; } + .col-xs-1 { width: 8.33333333%; } + .col-xs-auto { width: auto; } + .hide-xs { display: none !important; } + .show-xs { display: block !important; } } + +.hero { display: -ms-flexbox; display: flex; -ms-flex-direction: column; flex-direction: column; -ms-flex-pack: justify; justify-content: space-between; padding-bottom: 4rem; padding-top: 4rem; } + +.hero.hero-sm { padding-bottom: 2rem; padding-top: 2rem; } + +.hero.hero-lg { padding-bottom: 8rem; padding-top: 8rem; } + +.hero .hero-body { padding: 0.4rem; } + +.navbar { -ms-flex-align: stretch; align-items: stretch; display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; -ms-flex-pack: justify; justify-content: space-between; } + +.navbar .navbar-section { -ms-flex-align: center; align-items: center; display: -ms-flexbox; display: flex; -ms-flex: 1 0 0px; flex: 1 0 0; } + +.navbar .navbar-section:not(:first-child):last-child { -ms-flex-pack: end; justify-content: flex-end; } + +.navbar .navbar-center { -ms-flex-align: center; align-items: center; display: -ms-flexbox; display: flex; -ms-flex: 0 0 auto; flex: 0 0 auto; } + +.navbar .navbar-brand { font-size: 0.9rem; text-decoration: none; } + +.accordion input:checked ~ .accordion-header .icon, .accordion[open] .accordion-header .icon { transform: rotate(90deg); } + +.accordion input:checked ~ .accordion-body, .accordion[open] .accordion-body { max-height: 50rem; } + +.accordion .accordion-header { display: block; padding: 0.2rem 0.4rem; } + +.accordion .accordion-header .icon { transition: transform .25s; } + +.accordion .accordion-body { margin-bottom: 0.4rem; max-height: 0; overflow: hidden; transition: max-height .25s; } + +summary.accordion-header::-webkit-details-marker { display: none; } + +.avatar { font-size: 0.8rem; height: 1.6rem; width: 1.6rem; background: #3085EE; border-radius: 50%; color: rgba(255, 255, 255, 0.85); display: inline-block; font-weight: 300; line-height: 1.25; margin: 0; position: relative; vertical-align: middle; } + +.avatar.avatar-xs { font-size: 0.4rem; height: 0.8rem; width: 0.8rem; } + +.avatar.avatar-sm { font-size: 0.6rem; height: 1.2rem; width: 1.2rem; } + +.avatar.avatar-lg { font-size: 1.2rem; height: 2.4rem; width: 2.4rem; } + +.avatar.avatar-xl { font-size: 1.6rem; height: 3.2rem; width: 3.2rem; } + +.avatar img { border-radius: 50%; height: 100%; position: relative; width: 100%; z-index: 1; } + +.avatar .avatar-icon, .avatar .avatar-presence { background: #fff; bottom: 14.64%; height: 50%; padding: 0.1rem; position: absolute; right: 14.64%; transform: translate(50%, 50%); width: 50%; z-index: 2; } + +.avatar .avatar-presence { background: #acb3c2; box-shadow: 0 0 0 0.1rem #fff; border-radius: 50%; height: .5em; width: .5em; } + +.avatar .avatar-presence.online { background: #32b643; } + +.avatar .avatar-presence.busy { background: #e85600; } + +.avatar .avatar-presence.away { background: #ffb700; } + +.avatar[data-initial]::before { color: currentColor; content: attr(data-initial); left: 50%; position: absolute; top: 50%; transform: translate(-50%, -50%); z-index: 1; } + +.badge { position: relative; white-space: nowrap; } + +.badge[data-badge]::after, .badge:not([data-badge])::after { background: #3085EE; background-clip: padding-box; border-radius: .5rem; box-shadow: 0 0 0 0.1rem #fff; color: #fff; content: attr(data-badge); display: inline-block; transform: translate(-0.05rem, -0.5rem); } + +.badge[data-badge]::after { font-size: 0.7rem; height: .9rem; line-height: 1; min-width: .9rem; padding: .1rem .2rem; text-align: center; white-space: nowrap; } + +.badge:not([data-badge])::after, .badge[data-badge=""]::after { height: 6px; min-width: 6px; padding: 0; width: 6px; } + +.badge.btn::after, .badge.button::after { position: absolute; top: 0; right: 0; transform: translate(50%, -50%); } + +.badge.avatar::after { position: absolute; top: 14.64%; right: 14.64%; transform: translate(50%, -50%); z-index: 100; } + +.breadcrumb { list-style: none; margin: 0.2rem 0; padding: 0.2rem 0; } + +.breadcrumb .breadcrumb-item { color: #667189; display: inline-block; margin: 0; padding: 0.2rem 0; } + +.breadcrumb .breadcrumb-item:not(:last-child) { margin-right: 0.2rem; } + +.breadcrumb .breadcrumb-item:not(:last-child) a { color: #667189; } + +.breadcrumb .breadcrumb-item:not(:first-child)::before { color: #667189; content: "/"; padding-right: 0.4rem; } + +.bar { background: #f0f1f4; border-radius: 0.1rem; display: -ms-flexbox; display: flex; -ms-flex-wrap: nowrap; flex-wrap: nowrap; height: 0.8rem; width: 100%; } + +.bar.bar-sm { height: 0.2rem; } + +.bar .bar-item { background: #3085EE; color: #fff; display: block; font-size: 0.7rem; -ms-flex-negative: 0; flex-shrink: 0; line-height: 0.8rem; height: 100%; position: relative; text-align: center; width: 0; } + +.bar .bar-item:first-child { border-bottom-left-radius: 0.1rem; border-top-left-radius: 0.1rem; } + +.bar .bar-item:last-child { border-bottom-right-radius: 0.1rem; border-top-right-radius: 0.1rem; -ms-flex-negative: 1; flex-shrink: 1; } + +.bar-slider { height: 0.1rem; margin: 0.4rem 0; position: relative; } + +.bar-slider .bar-item { left: 0; padding: 0; position: absolute; } + +.bar-slider .bar-item:not(:last-child):first-child { background: #f0f1f4; z-index: 1; } + +.bar-slider .bar-slider-btn { background: #3085EE; border: 0; border-radius: 50%; height: 0.6rem; padding: 0; position: absolute; right: 0; top: 50%; transform: translate(50%, -50%); width: 0.6rem; } + +.bar-slider .bar-slider-btn:active { box-shadow: 0 0 0 0.1rem #3085EE; } + +.card { background: #fff; border: 0.05rem solid #e7e9ed; border-radius: 0.1rem; display: -ms-flexbox; display: flex; -ms-flex-direction: column; flex-direction: column; } + +.card .card-header, .card .card-body, .card .card-footer { padding: 0.8rem; padding-bottom: 0; } + +.card .card-header:last-child, .card .card-body:last-child, .card .card-footer:last-child { padding-bottom: 0.8rem; } + +.card .card-body { -ms-flex: 1 1 auto; flex: 1 1 auto; } + +.card .card-image { padding-top: 0.8rem; } + +.card .card-image:first-child { padding-top: 0; } + +.card .card-image:first-child img { border-top-left-radius: 0.1rem; border-top-right-radius: 0.1rem; } + +.card .card-image:last-child img { border-bottom-left-radius: 0.1rem; border-bottom-right-radius: 0.1rem; } + +.chip { -ms-flex-align: center; align-items: center; background: #f0f1f4; border-radius: 5rem; display: -ms-inline-flexbox; display: inline-flex; font-size: 90%; height: 1.2rem; line-height: 0.8rem; margin: 0.1rem; max-width: 320px; overflow: hidden; padding: 0.2rem 0.4rem; text-decoration: none; text-overflow: ellipsis; vertical-align: middle; white-space: nowrap; } + +.chip.active { background: #3085EE; color: #fff; } + +.chip .avatar { margin-left: -0.4rem; margin-right: 0.2rem; } + +.chip .btn-clear { border-radius: 50%; transform: scale(0.75); } + +.dropdown { display: inline-block; position: relative; } + +.dropdown .menu { animation: slide-down .15s ease 1; display: none; left: 0; max-height: 50vh; overflow-y: auto; position: absolute; top: 100%; } + +.dropdown.dropdown-right .menu { left: auto; right: 0; } + +.dropdown.active .menu, .dropdown .dropdown-toggle:focus + .menu, .dropdown .menu:hover { display: block; } + +.dropdown .btn-group .dropdown-toggle:nth-last-child(2) { border-bottom-right-radius: 0.1rem; border-top-right-radius: 0.1rem; } + +.empty { background: #f8f9fa; border-radius: 0.1rem; color: #667189; text-align: center; padding: 3.2rem 1.6rem; } + +.empty .empty-icon { margin-bottom: 0.8rem; } + +.empty .empty-title, .empty .empty-subtitle { margin: 0.4rem auto; } + +.empty .empty-action { margin-top: 0.8rem; } + +.menu { box-shadow: 0 0.05rem 0.2rem rgba(69, 77, 93, 0.3); background: #fff; border-radius: 0.1rem; list-style: none; margin: 0; min-width: 180px; padding: 0.4rem; transform: translateY(0.2rem); z-index: 300; } + +.menu.menu-nav { background: transparent; box-shadow: none; } + +.menu .menu-item { margin-top: 0; padding: 0 0.4rem; position: relative; text-decoration: none; } + +.menu .menu-item > a { border-radius: 0.1rem; color: inherit; display: block; margin: 0 -0.4rem; padding: 0.2rem 0.4rem; text-decoration: none; } + +.menu .menu-item > a:focus, .menu .menu-item > a:hover { background: #e1edfd; color: #3085EE; } + +.menu .menu-item > a:active, .menu .menu-item > a.active { background: #e1edfd; color: #3085EE; } + +.menu .menu-item .form-checkbox, .menu .menu-item .form-radio, .menu .menu-item .form-switch { margin: 0.1rem 0; } + +.menu .menu-item + .menu-item { margin-top: 0.2rem; } + +.menu .menu-badge { -ms-flex-align: center; align-items: center; display: -ms-flexbox; display: flex; height: 100%; position: absolute; right: 0; top: 0; } + +.menu .menu-badge .label { margin-right: 0.4rem; } + +.modal { -ms-flex-align: center; align-items: center; bottom: 0; display: none; -ms-flex-pack: center; justify-content: center; left: 0; opacity: 0; overflow: hidden; padding: 0.4rem; position: fixed; right: 0; top: 0; } + +.modal:target, .modal.active { display: -ms-flexbox; display: flex; opacity: 1; z-index: 400; } + +.modal:target .modal-overlay, .modal.active .modal-overlay { background: rgba(248, 249, 250, 0.75); bottom: 0; cursor: default; display: block; left: 0; position: absolute; right: 0; top: 0; } + +.modal:target .modal-container, .modal.active .modal-container { animation: slide-down .2s ease 1; z-index: 1; } + +.modal.modal-sm .modal-container { max-width: 320px; padding: 0 0.4rem; } + +.modal.modal-lg .modal-overlay { background: #fff; } + +.modal.modal-lg .modal-container { box-shadow: none; max-width: 960px; } + +.modal-container { box-shadow: 0 0.2rem 0.5rem rgba(69, 77, 93, 0.3); background: #fff; border-radius: 0.1rem; display: -ms-flexbox; display: flex; -ms-flex-direction: column; flex-direction: column; max-height: 75vh; max-width: 640px; padding: 0 0.8rem; width: 100%; } + +.modal-container.modal-fullheight { max-height: 100vh; } + +.modal-container .modal-header { color: #454d5d; padding: 0.8rem; } + +.modal-container .modal-body { overflow-y: auto; padding: 0.8rem; position: relative; } + +.modal-container .modal-footer { padding: 0.8rem; text-align: right; } + +.nav { display: -ms-flexbox; display: flex; -ms-flex-direction: column; flex-direction: column; list-style: none; margin: 0.2rem 0; } + +.nav .nav-item a { color: #667189; padding: 0.2rem 0.4rem; text-decoration: none; } + +.nav .nav-item a:focus, .nav .nav-item a:hover { color: #3085EE; } + +.nav .nav-item.active > a { color: #50596c; font-weight: bold; } + +.nav .nav-item.active > a:focus, .nav .nav-item.active > a:hover { color: #3085EE; } + +.nav .nav { margin-bottom: 0.4rem; margin-left: 0.8rem; } + +.pagination { display: -ms-flexbox; display: flex; list-style: none; margin: 0.2rem 0; padding: 0.2rem 0; } + +.pagination .page-item { margin: 0.2rem 0.05rem; } + +.pagination .page-item span { display: inline-block; padding: 0.2rem 0.2rem; } + +.pagination .page-item a { border-radius: 0.1rem; display: inline-block; padding: 0.2rem 0.4rem; text-decoration: none; } + +.pagination .page-item a:focus, .pagination .page-item a:hover { color: #3085EE; } + +.pagination .page-item.disabled a { cursor: default; opacity: .5; pointer-events: none; } + +.pagination .page-item.active a { background: #3085EE; color: #fff; } + +.pagination .page-item.page-prev, .pagination .page-item.page-next { -ms-flex: 1 0 50%; flex: 1 0 50%; } + +.pagination .page-item.page-next { text-align: right; } + +.pagination .page-item .page-item-title { margin: 0; } + +.pagination .page-item .page-item-subtitle { margin: 0; opacity: .5; } + +.panel { border: 0.05rem solid #e7e9ed; border-radius: 0.1rem; display: -ms-flexbox; display: flex; -ms-flex-direction: column; flex-direction: column; } + +.panel .panel-header, .panel .panel-footer { -ms-flex: 0 0 auto; flex: 0 0 auto; padding: 0.8rem; } + +.panel .panel-nav { -ms-flex: 0 0 auto; flex: 0 0 auto; } + +.panel .panel-body { -ms-flex: 1 1 auto; flex: 1 1 auto; overflow-y: auto; padding: 0 0.8rem; } + +.popover { display: inline-block; position: relative; } + +.popover .popover-container { left: 50%; opacity: 0; padding: 0.4rem; position: absolute; top: 0; transform: translate(-50%, -50%) scale(0); transition: transform .2s; width: 320px; z-index: 300; } + +.popover *:focus + .popover-container, .popover:hover .popover-container { display: block; opacity: 1; transform: translate(-50%, -100%) scale(1); } + +.popover.popover-right .popover-container { left: 100%; top: 50%; } + +.popover.popover-right *:focus + .popover-container, .popover.popover-right:hover .popover-container { transform: translate(0, -50%) scale(1); } + +.popover.popover-bottom .popover-container { left: 50%; top: 100%; } + +.popover.popover-bottom *:focus + .popover-container, .popover.popover-bottom:hover .popover-container { transform: translate(-50%, 0) scale(1); } + +.popover.popover-left .popover-container { left: 0; top: 50%; } + +.popover.popover-left *:focus + .popover-container, .popover.popover-left:hover .popover-container { transform: translate(-100%, -50%) scale(1); } + +.popover .card { box-shadow: 0 0.2rem 0.5rem rgba(69, 77, 93, 0.3); border: 0; } + +.step { display: -ms-flexbox; display: flex; -ms-flex-wrap: nowrap; flex-wrap: nowrap; list-style: none; margin: 0.2rem 0; width: 100%; } + +.step .step-item { -ms-flex: 1 1 0px; flex: 1 1 0; margin-top: 0; min-height: 1rem; text-align: center; position: relative; } + +.step .step-item:not(:first-child)::before { background: #3085EE; content: ""; height: 2px; left: -50%; position: absolute; top: 9px; width: 100%; } + +.step .step-item a { color: #3085EE; display: inline-block; padding: 20px 10px 0; text-decoration: none; } + +.step .step-item a::before { background: #3085EE; border: 0.1rem solid #fff; border-radius: 50%; content: ""; display: block; height: 0.6rem; left: 50%; position: absolute; top: 0.2rem; transform: translateX(-50%); width: 0.6rem; z-index: 1; } + +.step .step-item.active a::before { background: #fff; border: 0.1rem solid #3085EE; } + +.step .step-item.active ~ .step-item::before { background: #e7e9ed; } + +.step .step-item.active ~ .step-item a { color: #acb3c2; } + +.step .step-item.active ~ .step-item a::before { background: #e7e9ed; } + +.tab { -ms-flex-align: center; align-items: center; border-bottom: 0.05rem solid #e7e9ed; display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; list-style: none; margin: 0.2rem 0 0.15rem 0; } + +.tab .tab-item { margin-top: 0; } + +.tab .tab-item a { border-bottom: 0.1rem solid transparent; color: inherit; display: block; margin: 0 0.4rem 0 0; padding: 0.4rem 0.2rem 0.3rem 0.2rem; text-decoration: none; } + +.tab .tab-item a:focus, .tab .tab-item a:hover { color: #3085EE; } + +.tab .tab-item.active a, .tab .tab-item a.active { border-bottom-color: #3085EE; color: #3085EE; } + +.tab .tab-item.tab-action { -ms-flex: 1 0 auto; flex: 1 0 auto; text-align: right; } + +.tab .tab-item .btn-clear { margin-top: -0.2rem; } + +.tab.tab-block .tab-item { -ms-flex: 1 0 0px; flex: 1 0 0; text-align: center; } + +.tab.tab-block .tab-item a { margin: 0; } + +.tab.tab-block .tab-item .badge[data-badge]::after { position: absolute; right: 0.1rem; top: 0.1rem; transform: translate(0, 0); } + +.tab:not(.tab-block) .badge { padding-right: 0; } + +.tile { -ms-flex-line-pack: justify; align-content: space-between; -ms-flex-align: start; align-items: flex-start; display: -ms-flexbox; display: flex; } + +.tile .tile-icon, .tile .tile-action { -ms-flex: 0 0 auto; flex: 0 0 auto; } + +.tile .tile-content { -ms-flex: 1 1 auto; flex: 1 1 auto; } + +.tile .tile-content:not(:first-child) { padding-left: 0.4rem; } + +.tile .tile-content:not(:last-child) { padding-right: 0.4rem; } + +.tile .tile-title, .tile .tile-subtitle { line-height: 1.2rem; } + +.tile.tile-centered { -ms-flex-align: center; align-items: center; } + +.tile.tile-centered .tile-content { overflow: hidden; } + +.tile.tile-centered .tile-title, .tile.tile-centered .tile-subtitle { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-bottom: 0; } + +.toast { background: rgba(69, 77, 93, 0.95); border-color: #454d5d; border: 0.05rem solid #454d5d; border-radius: 0.1rem; color: #fff; display: block; padding: 0.4rem; width: 100%; } + +.toast.toast-primary { background: rgba(48, 133, 238, 0.95); border-color: #3085EE; } + +.toast.toast-success { background: rgba(50, 182, 67, 0.95); border-color: #32b643; } + +.toast.toast-warning { background: rgba(255, 183, 0, 0.95); border-color: #ffb700; } + +.toast.toast-error { background: rgba(232, 86, 0, 0.95); border-color: #e85600; } + +.toast a { color: #fff; text-decoration: underline; } + +.toast a:focus, .toast a:hover, .toast a:active, .toast a.active { opacity: .75; } + +.toast .btn-clear { margin: 0.1rem; } + +.toast p:last-child { margin-bottom: 0; } + +.tooltip { position: relative; } + +.tooltip::after { background: rgba(69, 77, 93, 0.95); border-radius: 0.1rem; bottom: 100%; color: #fff; content: attr(data-tooltip); display: block; font-size: 0.7rem; left: 50%; max-width: 320px; opacity: 0; overflow: hidden; padding: 0.2rem 0.4rem; pointer-events: none; position: absolute; text-overflow: ellipsis; transform: translate(-50%, 0.4rem); transition: opacity .2s, transform .2s; white-space: pre; z-index: 300; } + +.tooltip:focus::after, .tooltip:hover::after { opacity: 1; transform: translate(-50%, -0.2rem); } + +.tooltip[disabled], .tooltip.disabled { pointer-events: auto; } + +.tooltip.tooltip-right::after { bottom: 50%; left: 100%; transform: translate(-0.2rem, 50%); } + +.tooltip.tooltip-right:focus::after, .tooltip.tooltip-right:hover::after { transform: translate(0.2rem, 50%); } + +.tooltip.tooltip-bottom::after { bottom: auto; top: 100%; transform: translate(-50%, -0.4rem); } + +.tooltip.tooltip-bottom:focus::after, .tooltip.tooltip-bottom:hover::after { transform: translate(-50%, 0.2rem); } + +.tooltip.tooltip-left::after { bottom: 50%; left: auto; right: 100%; transform: translate(0.4rem, 50%); } + +.tooltip.tooltip-left:focus::after, .tooltip.tooltip-left:hover::after { transform: translate(-0.2rem, 50%); } + +@keyframes loading { 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } } + +@keyframes slide-down { 0% { opacity: 0; + transform: translateY(-1.6rem); } + 100% { opacity: 1; + transform: translateY(0); } } + +.text-primary { color: #3085EE !important; } + +a.text-primary:focus, a.text-primary:hover { color: #1877ec; } + +a.text-primary:visited { color: #4893f0; } + +.text-secondary { color: #d3e5fb !important; } + +a.text-secondary:focus, a.text-secondary:hover { color: #bbd7f9; } + +a.text-secondary:visited { color: #eaf3fd; } + +.text-gray { color: #acb3c2 !important; } + +a.text-gray:focus, a.text-gray:hover { color: #9ea6b7; } + +a.text-gray:visited { color: #bbc1cd; } + +.text-light { color: #fff !important; } + +a.text-light:focus, a.text-light:hover { color: #f2f2f2; } + +a.text-light:visited { color: white; } + +.text-dark { color: #50596c !important; } + +a.text-dark:focus, a.text-dark:hover { color: #454d5d; } + +a.text-dark:visited { color: #5b657a; } + +.text-success { color: #32b643 !important; } + +a.text-success:focus, a.text-success:hover { color: #2da23c; } + +a.text-success:visited { color: #39c94b; } + +.text-warning { color: #ffb700 !important; } + +a.text-warning:focus, a.text-warning:hover { color: #e6a500; } + +a.text-warning:visited { color: #ffbe1a; } + +.text-error { color: #e85600 !important; } + +a.text-error:focus, a.text-error:hover { color: #cf4d00; } + +a.text-error:visited { color: #ff6003; } + +.bg-primary { background: #3085EE !important; color: #fff; } + +.bg-secondary { background: #e1edfd !important; } + +.bg-dark { background: #454d5d !important; color: #fff; } + +.bg-gray { background: #f8f9fa !important; } + +.bg-success { background: #32b643 !important; color: #fff; } + +.bg-warning { background: #ffb700 !important; color: #fff; } + +.bg-error { background: #e85600 !important; color: #fff; } + +.c-hand { cursor: pointer; } + +.c-move { cursor: move; } + +.c-zoom-in { cursor: zoom-in; } + +.c-zoom-out { cursor: zoom-out; } + +.c-not-allowed { cursor: not-allowed; } + +.c-auto { cursor: auto; } + +.d-block { display: block; } + +.d-inline { display: inline; } + +.d-inline-block { display: inline-block; } + +.d-flex { display: -ms-flexbox; display: flex; } + +.d-inline-flex { display: -ms-inline-flexbox; display: inline-flex; } + +.d-none, .d-hide { display: none !important; } + +.d-visible { visibility: visible; } + +.d-invisible { visibility: hidden; } + +.text-hide { background: transparent; border: 0; color: transparent; font-size: 0; line-height: 0; text-shadow: none; } + +.text-assistive { border: 0; clip: rect(0, 0, 0, 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } + +.divider, .divider-vert { display: block; position: relative; } + +.divider[data-content]::after, .divider-vert[data-content]::after { background: #fff; color: #acb3c2; content: attr(data-content); display: inline-block; font-size: 0.7rem; padding: 0 0.4rem; transform: translateY(-0.65rem); } + +.divider { border-top: 0.05rem solid #fefefe; height: 0.05rem; margin: 0.4rem 0; } + +.divider[data-content] { margin: 0.8rem 0; } + +.divider-vert { display: block; padding: 0.8rem; } + +.divider-vert::before { border-left: 0.05rem solid #e7e9ed; bottom: 0.4rem; content: ""; display: block; left: 50%; position: absolute; top: 0.4rem; transform: translateX(-50%); } + +.divider-vert[data-content]::after { left: 50%; padding: 0.2rem 0; position: absolute; top: 50%; transform: translate(-50%, -50%); } + +.loading { color: transparent !important; min-height: 0.8rem; pointer-events: none; position: relative; } + +.loading::after { animation: loading 500ms infinite linear; border: 0.1rem solid #3085EE; border-radius: 50%; border-right-color: transparent; border-top-color: transparent; content: ""; display: block; height: 0.8rem; left: 50%; margin-left: -0.4rem; margin-top: -0.4rem; position: absolute; top: 50%; width: 0.8rem; z-index: 1; } + +.loading.loading-lg { min-height: 2rem; } + +.loading.loading-lg::after { height: 1.6rem; margin-left: -0.8rem; margin-top: -0.8rem; width: 1.6rem; } + +.clearfix::after { clear: both; content: ""; display: table; } + +.float-left { float: left !important; } + +.float-right { float: right !important; } + +.p-relative { position: relative !important; } + +.p-absolute { position: absolute !important; } + +.p-fixed { position: fixed !important; } + +.p-sticky { position: -webkit-sticky !important; position: sticky !important; } + +.p-centered { display: block; float: none; margin-left: auto; margin-right: auto; } + +.flex-centered { -ms-flex-align: center; align-items: center; display: -ms-flexbox; display: flex; -ms-flex-pack: center; justify-content: center; } + +.m-0 { margin: 0 !important; } + +.mb-0 { margin-bottom: 0 !important; } + +.ml-0 { margin-left: 0 !important; } + +.mr-0 { margin-right: 0 !important; } + +.mt-0 { margin-top: 0 !important; } + +.mx-0 { margin-left: 0 !important; margin-right: 0 !important; } + +.my-0 { margin-bottom: 0 !important; margin-top: 0 !important; } + +.m-1 { margin: 0.2rem !important; } + +.mb-1 { margin-bottom: 0.2rem !important; } + +.ml-1 { margin-left: 0.2rem !important; } + +.mr-1 { margin-right: 0.2rem !important; } + +.mt-1 { margin-top: 0.2rem !important; } + +.mx-1 { margin-left: 0.2rem !important; margin-right: 0.2rem !important; } + +.my-1 { margin-bottom: 0.2rem !important; margin-top: 0.2rem !important; } + +.m-2 { margin: 0.4rem !important; } + +.mb-2 { margin-bottom: 0.4rem !important; } + +.ml-2 { margin-left: 0.4rem !important; } + +.mr-2 { margin-right: 0.4rem !important; } + +.mt-2 { margin-top: 0.4rem !important; } + +.mx-2 { margin-left: 0.4rem !important; margin-right: 0.4rem !important; } + +.my-2 { margin-bottom: 0.4rem !important; margin-top: 0.4rem !important; } + +.p-0 { padding: 0 !important; } + +.pb-0 { padding-bottom: 0 !important; } + +.pl-0 { padding-left: 0 !important; } + +.pr-0 { padding-right: 0 !important; } + +.pt-0 { padding-top: 0 !important; } + +.px-0 { padding-left: 0 !important; padding-right: 0 !important; } + +.py-0 { padding-bottom: 0 !important; padding-top: 0 !important; } + +.p-1 { padding: 0.2rem !important; } + +.pb-1 { padding-bottom: 0.2rem !important; } + +.pl-1 { padding-left: 0.2rem !important; } + +.pr-1 { padding-right: 0.2rem !important; } + +.pt-1 { padding-top: 0.2rem !important; } + +.px-1 { padding-left: 0.2rem !important; padding-right: 0.2rem !important; } + +.py-1 { padding-bottom: 0.2rem !important; padding-top: 0.2rem !important; } + +.p-2 { padding: 0.4rem !important; } + +.pb-2 { padding-bottom: 0.4rem !important; } + +.pl-2 { padding-left: 0.4rem !important; } + +.pr-2 { padding-right: 0.4rem !important; } + +.pt-2 { padding-top: 0.4rem !important; } + +.px-2 { padding-left: 0.4rem !important; padding-right: 0.4rem !important; } + +.py-2 { padding-bottom: 0.4rem !important; padding-top: 0.4rem !important; } + +.s-rounded { border-radius: 0.1rem; } + +.s-circle { border-radius: 50%; } + +.text-left { text-align: left; } + +.text-right { text-align: right; } + +.text-center { text-align: center; } + +.text-justify { text-align: justify; } + +.text-lowercase { text-transform: lowercase; } + +.text-uppercase { text-transform: uppercase; } + +.text-capitalize { text-transform: capitalize; } + +.text-normal { font-weight: normal; } + +.text-bold { font-weight: bold; } + +.text-italic { font-style: italic; } + +.text-large { font-size: 1.2em; } + +.text-ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + +.text-clip { overflow: hidden; text-overflow: clip; white-space: nowrap; } + +.text-break { -webkit-hyphens: auto; -ms-hyphens: auto; hyphens: auto; word-break: break-word; word-wrap: break-word; } + +/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BlY3RyZS5jc3MiLCJzb3VyY2VzIjpbInNwZWN0cmUuc2NzcyIsInRoZW1lL192YXJpYWJsZXMuc2NzcyIsInNwZWN0cmUvX3ZhcmlhYmxlcy5zY3NzIiwic3BlY3RyZS9fbWl4aW5zLnNjc3MiLCJzcGVjdHJlL21peGlucy9fYXZhdGFyLnNjc3MiLCJzcGVjdHJlL21peGlucy9fYnV0dG9uLnNjc3MiLCJzcGVjdHJlL21peGlucy9fY2xlYXJmaXguc2NzcyIsInNwZWN0cmUvbWl4aW5zL19jb2xvci5zY3NzIiwic3BlY3RyZS9taXhpbnMvX2xhYmVsLnNjc3MiLCJzcGVjdHJlL21peGlucy9fcG9zaXRpb24uc2NzcyIsInNwZWN0cmUvbWl4aW5zL19zaGFkb3cuc2NzcyIsInNwZWN0cmUvbWl4aW5zL190ZXh0LnNjc3MiLCJzcGVjdHJlL21peGlucy9fdG9hc3Quc2NzcyIsInNwZWN0cmUvX25vcm1hbGl6ZS5zY3NzIiwic3BlY3RyZS9fYmFzZS5zY3NzIiwic3BlY3RyZS9fdHlwb2dyYXBoeS5zY3NzIiwic3BlY3RyZS9fYXNpYW4uc2NzcyIsInNwZWN0cmUvX3RhYmxlcy5zY3NzIiwic3BlY3RyZS9fYnV0dG9ucy5zY3NzIiwic3BlY3RyZS9fZm9ybXMuc2NzcyIsInNwZWN0cmUvX2xhYmVscy5zY3NzIiwic3BlY3RyZS9fY29kZXMuc2NzcyIsInNwZWN0cmUvX21lZGlhLnNjc3MiLCJzcGVjdHJlL19sYXlvdXQuc2NzcyIsInNwZWN0cmUvX2hlcm8uc2NzcyIsInNwZWN0cmUvX25hdmJhci5zY3NzIiwic3BlY3RyZS9fYWNjb3JkaW9ucy5zY3NzIiwic3BlY3RyZS9fYXZhdGFycy5zY3NzIiwic3BlY3RyZS9fYmFkZ2VzLnNjc3MiLCJzcGVjdHJlL19icmVhZGNydW1icy5zY3NzIiwic3BlY3RyZS9fYmFycy5zY3NzIiwic3BlY3RyZS9fY2FyZHMuc2NzcyIsInNwZWN0cmUvX2NoaXBzLnNjc3MiLCJzcGVjdHJlL19kcm9wZG93bnMuc2NzcyIsInNwZWN0cmUvX2VtcHR5LnNjc3MiLCJzcGVjdHJlL19tZW51cy5zY3NzIiwic3BlY3RyZS9fbW9kYWxzLnNjc3MiLCJzcGVjdHJlL19uYXZzLnNjc3MiLCJzcGVjdHJlL19wYWdpbmF0aW9uLnNjc3MiLCJzcGVjdHJlL19wYW5lbHMuc2NzcyIsInNwZWN0cmUvX3BvcG92ZXJzLnNjc3MiLCJzcGVjdHJlL19zdGVwcy5zY3NzIiwic3BlY3RyZS9fdGFicy5zY3NzIiwic3BlY3RyZS9fdGlsZXMuc2NzcyIsInNwZWN0cmUvX3RvYXN0cy5zY3NzIiwic3BlY3RyZS9fdG9vbHRpcHMuc2NzcyIsInNwZWN0cmUvX2FuaW1hdGlvbnMuc2NzcyIsInNwZWN0cmUvX3V0aWxpdGllcy5zY3NzIiwic3BlY3RyZS91dGlsaXRpZXMvX2NvbG9ycy5zY3NzIiwic3BlY3RyZS91dGlsaXRpZXMvX2N1cnNvcnMuc2NzcyIsInNwZWN0cmUvdXRpbGl0aWVzL19kaXNwbGF5LnNjc3MiLCJzcGVjdHJlL3V0aWxpdGllcy9fZGl2aWRlci5zY3NzIiwic3BlY3RyZS91dGlsaXRpZXMvX2xvYWRpbmcuc2NzcyIsInNwZWN0cmUvdXRpbGl0aWVzL19wb3NpdGlvbi5zY3NzIiwic3BlY3RyZS91dGlsaXRpZXMvX3NoYXBlcy5zY3NzIiwic3BlY3RyZS91dGlsaXRpZXMvX3RleHQuc2NzcyIsInRoZW1lL19leHRlbnNpb25zLnNjc3MiXSwic291cmNlc0NvbnRlbnQiOlsiLy8gVmFyaWFibGVzIGFuZCBtaXhpbnNcbkBpbXBvcnQgXCJ0aGVtZS92YXJpYWJsZXNcIjtcbkBpbXBvcnQgXCJzcGVjdHJlL3ZhcmlhYmxlc1wiO1xuQGltcG9ydCBcInNwZWN0cmUvbWl4aW5zXCI7XG5cbi8qISBTcGVjdHJlLmNzcyB2I3skdmVyc2lvbn0gfCBNSVQgTGljZW5zZSB8IGdpdGh1Yi5jb20vcGljdHVyZXBhbjIvc3BlY3RyZSAqL1xuLy8gUmVzZXQgYW5kIGRlcGVuZGVuY2llc1xuQGltcG9ydCBcInNwZWN0cmUvbm9ybWFsaXplXCI7XG5AaW1wb3J0IFwic3BlY3RyZS9iYXNlXCI7XG5cbi8vIEVsZW1lbnRzXG5AaW1wb3J0IFwic3BlY3RyZS90eXBvZ3JhcGh5XCI7XG5AaW1wb3J0IFwic3BlY3RyZS9hc2lhblwiO1xuQGltcG9ydCBcInNwZWN0cmUvdGFibGVzXCI7XG5AaW1wb3J0IFwic3BlY3RyZS9idXR0b25zXCI7XG5AaW1wb3J0IFwic3BlY3RyZS9mb3Jtc1wiO1xuQGltcG9ydCBcInNwZWN0cmUvbGFiZWxzXCI7XG5AaW1wb3J0IFwic3BlY3RyZS9jb2Rlc1wiO1xuQGltcG9ydCBcInNwZWN0cmUvbWVkaWFcIjtcblxuLy8gTGF5b3V0XG5AaW1wb3J0IFwic3BlY3RyZS9sYXlvdXRcIjtcbkBpbXBvcnQgXCJzcGVjdHJlL2hlcm9cIjtcbkBpbXBvcnQgXCJzcGVjdHJlL25hdmJhclwiO1xuXG4vLyBDb21wb25lbnRzXG5AaW1wb3J0IFwic3BlY3RyZS9hY2NvcmRpb25zXCI7XG5AaW1wb3J0IFwic3BlY3RyZS9hdmF0YXJzXCI7XG5AaW1wb3J0IFwic3BlY3RyZS9iYWRnZXNcIjtcbkBpbXBvcnQgXCJzcGVjdHJlL2JyZWFkY3J1bWJzXCI7XG5AaW1wb3J0IFwic3BlY3RyZS9iYXJzXCI7XG5AaW1wb3J0IFwic3BlY3RyZS9jYXJkc1wiO1xuQGltcG9ydCBcInNwZWN0cmUvY2hpcHNcIjtcbkBpbXBvcnQgXCJzcGVjdHJlL2Ryb3Bkb3duc1wiO1xuQGltcG9ydCBcInNwZWN0cmUvZW1wdHlcIjtcbkBpbXBvcnQgXCJzcGVjdHJlL21lbnVzXCI7XG5AaW1wb3J0IFwic3BlY3RyZS9tb2RhbHNcIjtcbkBpbXBvcnQgXCJzcGVjdHJlL25hdnNcIjtcbkBpbXBvcnQgXCJzcGVjdHJlL3BhZ2luYXRpb25cIjtcbkBpbXBvcnQgXCJzcGVjdHJlL3BhbmVsc1wiO1xuQGltcG9ydCBcInNwZWN0cmUvcG9wb3ZlcnNcIjtcbkBpbXBvcnQgXCJzcGVjdHJlL3N0ZXBzXCI7XG5AaW1wb3J0IFwic3BlY3RyZS90YWJzXCI7XG5AaW1wb3J0IFwic3BlY3RyZS90aWxlc1wiO1xuQGltcG9ydCBcInNwZWN0cmUvdG9hc3RzXCI7XG5AaW1wb3J0IFwic3BlY3RyZS90b29sdGlwc1wiO1xuXG4vLyBVdGlsaXR5IGNsYXNzZXNcbkBpbXBvcnQgXCJzcGVjdHJlL2FuaW1hdGlvbnNcIjtcbkBpbXBvcnQgXCJzcGVjdHJlL3V0aWxpdGllc1wiO1xuXG4vLyBFeHRyYXNcbkBpbXBvcnQgXCJ0aGVtZS9leHRlbnNpb25zXCI7XG4iLCIvLyBTcGVjdHJlIE92ZXJyaWRlc1xuJHByaW1hcnktY29sb3I6ICMzMDg1RUUgIWRlZmF1bHQ7XG4kZGFyay1jb2xvcjogIzQ1NGQ1ZCAhZGVmYXVsdDtcbiRsaWdodC1jb2xvcjogI2ZmZiAhZGVmYXVsdDtcbiRncmF5LWNvbG9yOiBsaWdodGVuKCRkYXJrLWNvbG9yLCA0MCUpICFkZWZhdWx0O1xuJGdyYXktY29sb3ItZGFyazogZGFya2VuKCRncmF5LWNvbG9yLCAyNSUpICFkZWZhdWx0O1xuJGJvcmRlci1jb2xvcjogbGlnaHRlbigkZGFyay1jb2xvciwgNjAlKSAhZGVmYXVsdDtcbiRiZy1jb2xvcjogbGlnaHRlbigkZGFyay1jb2xvciwgNjYlKSAhZGVmYXVsdDtcbiRib2R5LWZvbnQtY29sb3I6IGxpZ2h0ZW4oJGRhcmstY29sb3IsIDUlKSAhZGVmYXVsdDtcblxuLy8gTGF5b3V0XG4kaG9yaXotcGFkZGluZzogMXJlbTtcbiR2ZXJ0LXBhZGRpbmc6IDJyZW0gMCAycmVtO1xuXG4vLyBGb250c1xuJG1pbi1yZXNwb25zaXZlLWZvbnQtc2l6ZTogMTZweDtcblxuLy8gSGVhZGVyXG4kaGVhZGVyLWhlaWdodC1sYXJnZTogNHJlbTtcbiRoZWFkZXItaGVpZ2h0LXNtYWxsOiAyLjNyZW07XG4kaGVhZGVyLWNvbG9yLWRhcms6ICMyMjI7XG4kaGVhZGVyLXRleHQtbGlnaHQ6ICRsaWdodC1jb2xvcjtcbiRoZWFkZXItdGV4dC1kYXJrOiBkYXJrZW4oJGRhcmstY29sb3IsIDE1JSk7XG5cbi8vIERyb3Bkb3duIE1lbnVcbiRkcm9wbWVudS1iZzogJGxpZ2h0LWNvbG9yO1xuJGRyb3BtZW51LWhvdmVyLXRleHQ6ICRwcmltYXJ5LWNvbG9yO1xuJGRyb3BtZW51LWhvcml6LXBhZGRpbmc6IDIwcHg7XG4kZHJvcG1lbnUtdmVydC1wYWRkaW5nOiA3cHg7XG4kZHJvcG1lbnUtY2hpbGQtcGFkZGluZzogMTBweDtcbiRkcm9wbWVudS1zaGFkb3c6IDAgM3B4IDVweCByZ2JhKDAsIDAsIDAsIDAuMSk7XG5cbi8vIE1vYmlsZSBNZW51XG4kbW9iaWxlLWNvbG9yLW1haW46ICRwcmltYXJ5LWNvbG9yO1xuJG1vYmlsZS1jb2xvci1hY3RpdmU6ICNGRkY7XG4kbW9iaWxlLWNvbG9yLWxpbms6ICNGRkY7XG4kbW9iaWxlLWJ1dHRvbi1oZWlnaHQ6IDI0cHg7XG4kbW9iaWxlLWJ1dHRvbi13aWR0aDogMjhweDsiLCIvLyBDb3JlIHZhcmlhYmxlc1xuJHZlcnNpb246IFwiMC41LjhcIjtcblxuLy8gQ29yZSBmZWF0dXJlc1xuJHJ0bDogZmFsc2UgIWRlZmF1bHQ7XG5cbi8vIENvcmUgY29sb3JzXG4kcHJpbWFyeS1jb2xvcjogIzU3NTVkOSAhZGVmYXVsdDtcbiRwcmltYXJ5LWNvbG9yLWRhcms6IGRhcmtlbigkcHJpbWFyeS1jb2xvciwgMyUpICFkZWZhdWx0O1xuJHByaW1hcnktY29sb3ItbGlnaHQ6IGxpZ2h0ZW4oJHByaW1hcnktY29sb3IsIDMlKSAhZGVmYXVsdDtcbiRzZWNvbmRhcnktY29sb3I6IGxpZ2h0ZW4oJHByaW1hcnktY29sb3IsIDM3LjUlKSAhZGVmYXVsdDtcbiRzZWNvbmRhcnktY29sb3ItZGFyazogZGFya2VuKCRzZWNvbmRhcnktY29sb3IsIDMlKSAhZGVmYXVsdDtcbiRzZWNvbmRhcnktY29sb3ItbGlnaHQ6IGxpZ2h0ZW4oJHNlY29uZGFyeS1jb2xvciwgMyUpICFkZWZhdWx0O1xuXG4vLyBHcmF5IGNvbG9yc1xuJGRhcmstY29sb3I6ICMzMDM3NDIgIWRlZmF1bHQ7XG4kbGlnaHQtY29sb3I6ICNmZmYgIWRlZmF1bHQ7XG4kZ3JheS1jb2xvcjogbGlnaHRlbigkZGFyay1jb2xvciwgNTUlKSAhZGVmYXVsdDtcbiRncmF5LWNvbG9yLWRhcms6IGRhcmtlbigkZ3JheS1jb2xvciwgMzAlKSAhZGVmYXVsdDtcbiRncmF5LWNvbG9yLWxpZ2h0OiBsaWdodGVuKCRncmF5LWNvbG9yLCAyMCUpICFkZWZhdWx0O1xuXG4kYm9yZGVyLWNvbG9yOiBsaWdodGVuKCRkYXJrLWNvbG9yLCA2NSUpICFkZWZhdWx0O1xuJGJvcmRlci1jb2xvci1kYXJrOiBkYXJrZW4oJGJvcmRlci1jb2xvciwgMTAlKSAhZGVmYXVsdDtcbiRib3JkZXItY29sb3ItbGlnaHQ6IGxpZ2h0ZW4oJGJvcmRlci1jb2xvciwgOCUpICFkZWZhdWx0O1xuJGJnLWNvbG9yOiBsaWdodGVuKCRkYXJrLWNvbG9yLCA3NSUpICFkZWZhdWx0O1xuJGJnLWNvbG9yLWRhcms6IGRhcmtlbigkYmctY29sb3IsIDMlKSAhZGVmYXVsdDtcbiRiZy1jb2xvci1saWdodDogJGxpZ2h0LWNvbG9yICFkZWZhdWx0O1xuXG4vLyBDb250cm9sIGNvbG9yc1xuJHN1Y2Nlc3MtY29sb3I6ICMzMmI2NDMgIWRlZmF1bHQ7XG4kd2FybmluZy1jb2xvcjogI2ZmYjcwMCAhZGVmYXVsdDtcbiRlcnJvci1jb2xvcjogI2U4NTYwMCAhZGVmYXVsdDtcblxuLy8gT3RoZXIgY29sb3JzXG4kY29kZS1jb2xvcjogI2Q3M2U0OCAhZGVmYXVsdDtcbiRoaWdobGlnaHQtY29sb3I6ICNmZmU5YjMgIWRlZmF1bHQ7XG4kYm9keS1iZzogJGJnLWNvbG9yLWxpZ2h0ICFkZWZhdWx0O1xuJGJvZHktZm9udC1jb2xvcjogbGlnaHRlbigkZGFyay1jb2xvciwgNSUpICFkZWZhdWx0O1xuJGxpbmstY29sb3I6ICRwcmltYXJ5LWNvbG9yICFkZWZhdWx0O1xuJGxpbmstY29sb3ItZGFyazogZGFya2VuKCRsaW5rLWNvbG9yLCAxMCUpICFkZWZhdWx0O1xuJGxpbmstY29sb3ItbGlnaHQ6IGxpZ2h0ZW4oJGxpbmstY29sb3IsIDEwJSkgIWRlZmF1bHQ7XG5cbi8vIEZvbnRzXG4vLyBDcmVkaXQ6IGh0dHBzOi8vd3d3LnNtYXNoaW5nbWFnYXppbmUuY29tLzIwMTUvMTEvdXNpbmctc3lzdGVtLXVpLWZvbnRzLXByYWN0aWNhbC1ndWlkZS9cbiRiYXNlLWZvbnQtZmFtaWx5OiAtYXBwbGUtc3lzdGVtLCBzeXN0ZW0tdWksIEJsaW5rTWFjU3lzdGVtRm9udCwgXCJTZWdvZSBVSVwiLCBSb2JvdG8gIWRlZmF1bHQ7XG4kbW9uby1mb250LWZhbWlseTogXCJTRiBNb25vXCIsIFwiU2Vnb2UgVUkgTW9ub1wiLCBcIlJvYm90byBNb25vXCIsIE1lbmxvLCBDb3VyaWVyLCBtb25vc3BhY2UgIWRlZmF1bHQ7XG4kZmFsbGJhY2stZm9udC1mYW1pbHk6IFwiSGVsdmV0aWNhIE5ldWVcIiwgc2Fucy1zZXJpZiAhZGVmYXVsdDtcbiRjamstemgtaGFucy1mb250LWZhbWlseTogJGJhc2UtZm9udC1mYW1pbHksIFwiUGluZ0ZhbmcgU0NcIiwgXCJIaXJhZ2lubyBTYW5zIEdCXCIsIFwiTWljcm9zb2Z0IFlhSGVpXCIsICRmYWxsYmFjay1mb250LWZhbWlseSAhZGVmYXVsdDtcbiRjamstemgtaGFudC1mb250LWZhbWlseTogJGJhc2UtZm9udC1mYW1pbHksIFwiUGluZ0ZhbmcgVENcIiwgXCJIaXJhZ2lubyBTYW5zIENOU1wiLCBcIk1pY3Jvc29mdCBKaGVuZ0hlaVwiLCAkZmFsbGJhY2stZm9udC1mYW1pbHkgIWRlZmF1bHQ7XG4kY2prLWpwLWZvbnQtZmFtaWx5OiAkYmFzZS1mb250LWZhbWlseSwgXCJIaXJhZ2lubyBTYW5zXCIsIFwiSGlyYWdpbm8gS2FrdSBHb3RoaWMgUHJvXCIsIFwiWXUgR290aGljXCIsIFl1R290aGljLCBNZWlyeW8sICRmYWxsYmFjay1mb250LWZhbWlseSAhZGVmYXVsdDtcbiRjamsta28tZm9udC1mYW1pbHk6ICRiYXNlLWZvbnQtZmFtaWx5LCBcIk1hbGd1biBHb3RoaWNcIiwgJGZhbGxiYWNrLWZvbnQtZmFtaWx5ICFkZWZhdWx0O1xuJGJvZHktZm9udC1mYW1pbHk6ICRiYXNlLWZvbnQtZmFtaWx5LCAkZmFsbGJhY2stZm9udC1mYW1pbHkgIWRlZmF1bHQ7XG5cbi8vIFVuaXQgc2l6ZXNcbiR1bml0LW86IC4wNXJlbSAhZGVmYXVsdDtcbiR1bml0LWg6IC4xcmVtICFkZWZhdWx0O1xuJHVuaXQtMTogLjJyZW0gIWRlZmF1bHQ7XG4kdW5pdC0yOiAuNHJlbSAhZGVmYXVsdDtcbiR1bml0LTM6IC42cmVtICFkZWZhdWx0O1xuJHVuaXQtNDogLjhyZW0gIWRlZmF1bHQ7XG4kdW5pdC01OiAxcmVtICFkZWZhdWx0O1xuJHVuaXQtNjogMS4ycmVtICFkZWZhdWx0O1xuJHVuaXQtNzogMS40cmVtICFkZWZhdWx0O1xuJHVuaXQtODogMS42cmVtICFkZWZhdWx0O1xuJHVuaXQtOTogMS44cmVtICFkZWZhdWx0O1xuJHVuaXQtMTA6IDJyZW0gIWRlZmF1bHQ7XG4kdW5pdC0xMjogMi40cmVtICFkZWZhdWx0O1xuJHVuaXQtMTY6IDMuMnJlbSAhZGVmYXVsdDtcblxuLy8gRm9udCBzaXplc1xuJGh0bWwtZm9udC1zaXplOiAyMHB4ICFkZWZhdWx0O1xuJGh0bWwtbGluZS1oZWlnaHQ6IDEuNSAhZGVmYXVsdDtcbiRmb250LXNpemU6IC44cmVtICFkZWZhdWx0O1xuJGZvbnQtc2l6ZS1zbTogLjdyZW0gIWRlZmF1bHQ7XG4kZm9udC1zaXplLWxnOiAuOXJlbSAhZGVmYXVsdDtcbiRsaW5lLWhlaWdodDogMS4ycmVtICFkZWZhdWx0O1xuXG4vLyBTaXplc1xuJGxheW91dC1zcGFjaW5nOiAkdW5pdC0yICFkZWZhdWx0O1xuJGxheW91dC1zcGFjaW5nLXNtOiAkdW5pdC0xICFkZWZhdWx0O1xuJGxheW91dC1zcGFjaW5nLWxnOiAkdW5pdC00ICFkZWZhdWx0O1xuJGJvcmRlci1yYWRpdXM6ICR1bml0LWggIWRlZmF1bHQ7XG4kYm9yZGVyLXdpZHRoOiAkdW5pdC1vICFkZWZhdWx0O1xuJGJvcmRlci13aWR0aC1sZzogJHVuaXQtaCAhZGVmYXVsdDtcbiRjb250cm9sLXNpemU6ICR1bml0LTkgIWRlZmF1bHQ7XG4kY29udHJvbC1zaXplLXNtOiAkdW5pdC03ICFkZWZhdWx0O1xuJGNvbnRyb2wtc2l6ZS1sZzogJHVuaXQtMTAgIWRlZmF1bHQ7XG4kY29udHJvbC1wYWRkaW5nLXg6ICR1bml0LTIgIWRlZmF1bHQ7XG4kY29udHJvbC1wYWRkaW5nLXgtc206ICR1bml0LTIgKiAuNzUgIWRlZmF1bHQ7XG4kY29udHJvbC1wYWRkaW5nLXgtbGc6ICR1bml0LTIgKiAxLjUgIWRlZmF1bHQ7XG4kY29udHJvbC1wYWRkaW5nLXk6ICgkY29udHJvbC1zaXplIC0gJGxpbmUtaGVpZ2h0KSAvIDIgLSAkYm9yZGVyLXdpZHRoICFkZWZhdWx0O1xuJGNvbnRyb2wtcGFkZGluZy15LXNtOiAoJGNvbnRyb2wtc2l6ZS1zbSAtICRsaW5lLWhlaWdodCkgLyAyIC0gJGJvcmRlci13aWR0aCAhZGVmYXVsdDtcbiRjb250cm9sLXBhZGRpbmcteS1sZzogKCRjb250cm9sLXNpemUtbGcgLSAkbGluZS1oZWlnaHQpIC8gMiAtICRib3JkZXItd2lkdGggIWRlZmF1bHQ7XG4kY29udHJvbC1pY29uLXNpemU6IC44cmVtICFkZWZhdWx0O1xuXG4kY29udHJvbC13aWR0aC14czogMTgwcHggIWRlZmF1bHQ7XG4kY29udHJvbC13aWR0aC1zbTogMzIwcHggIWRlZmF1bHQ7XG4kY29udHJvbC13aWR0aC1tZDogNjQwcHggIWRlZmF1bHQ7XG4kY29udHJvbC13aWR0aC1sZzogOTYwcHggIWRlZmF1bHQ7XG4kY29udHJvbC13aWR0aC14bDogMTI4MHB4ICFkZWZhdWx0O1xuXG4vLyBSZXNwb25zaXZlIGJyZWFrcG9pbnRzXG4kc2l6ZS14czogNDgwcHggIWRlZmF1bHQ7XG4kc2l6ZS1zbTogNjAwcHggIWRlZmF1bHQ7XG4kc2l6ZS1tZDogODQwcHggIWRlZmF1bHQ7XG4kc2l6ZS1sZzogOTYwcHggIWRlZmF1bHQ7XG4kc2l6ZS14bDogMTI4MHB4ICFkZWZhdWx0O1xuJHNpemUtMng6IDE0NDBweCAhZGVmYXVsdDtcblxuJHJlc3BvbnNpdmUtYnJlYWtwb2ludDogJHNpemUteHMgIWRlZmF1bHQ7XG5cbi8vIFotaW5kZXhcbiR6aW5kZXgtMDogMSAhZGVmYXVsdDtcbiR6aW5kZXgtMTogMTAwICFkZWZhdWx0O1xuJHppbmRleC0yOiAyMDAgIWRlZmF1bHQ7XG4kemluZGV4LTM6IDMwMCAhZGVmYXVsdDtcbiR6aW5kZXgtNDogNDAwICFkZWZhdWx0O1xuIiwiLy8gTWl4aW5zXG5AaW1wb3J0IFwibWl4aW5zL2F2YXRhclwiO1xuQGltcG9ydCBcIm1peGlucy9idXR0b25cIjtcbkBpbXBvcnQgXCJtaXhpbnMvY2xlYXJmaXhcIjtcbkBpbXBvcnQgXCJtaXhpbnMvY29sb3JcIjtcbkBpbXBvcnQgXCJtaXhpbnMvbGFiZWxcIjtcbkBpbXBvcnQgXCJtaXhpbnMvcG9zaXRpb25cIjtcbkBpbXBvcnQgXCJtaXhpbnMvc2hhZG93XCI7XG5AaW1wb3J0IFwibWl4aW5zL3RleHRcIjtcbkBpbXBvcnQgXCJtaXhpbnMvdG9hc3RcIjsiLCIvLyBBdmF0YXIgbWl4aW5cbkBtaXhpbiBhdmF0YXItYmFzZSgkc2l6ZTogJHVuaXQtOCkge1xuICBmb250LXNpemU6ICRzaXplIC8gMjtcbiAgaGVpZ2h0OiAkc2l6ZTtcbiAgd2lkdGg6ICRzaXplO1xufVxuIiwiLy8gQnV0dG9uIHZhcmlhbnQgbWl4aW5cbkBtaXhpbiBidXR0b24tdmFyaWFudCgkY29sb3I6ICRwcmltYXJ5LWNvbG9yKSB7XG4gIGJhY2tncm91bmQ6ICRjb2xvcjtcbiAgYm9yZGVyLWNvbG9yOiBkYXJrZW4oJGNvbG9yLCAzJSk7XG4gIGNvbG9yOiAkbGlnaHQtY29sb3I7XG4gICY6Zm9jdXMge1xuICAgIEBpbmNsdWRlIGNvbnRyb2wtc2hhZG93KCRjb2xvcik7XG4gIH1cbiAgJjpmb2N1cyxcbiAgJjpob3ZlciB7XG4gICAgYmFja2dyb3VuZDogZGFya2VuKCRjb2xvciwgMiUpO1xuICAgIGJvcmRlci1jb2xvcjogZGFya2VuKCRjb2xvciwgNSUpO1xuICAgIGNvbG9yOiAkbGlnaHQtY29sb3I7XG4gIH1cbiAgJjphY3RpdmUsXG4gICYuYWN0aXZlIHtcbiAgICBiYWNrZ3JvdW5kOiBkYXJrZW4oJGNvbG9yLCA3JSk7XG4gICAgYm9yZGVyLWNvbG9yOiBkYXJrZW4oJGNvbG9yLCAxMCUpO1xuICAgIGNvbG9yOiAkbGlnaHQtY29sb3I7XG4gIH1cbiAgJi5sb2FkaW5nIHtcbiAgICAmOjphZnRlciB7XG4gICAgICBib3JkZXItYm90dG9tLWNvbG9yOiAkbGlnaHQtY29sb3I7XG4gICAgICBib3JkZXItbGVmdC1jb2xvcjogJGxpZ2h0LWNvbG9yO1xuICAgIH1cbiAgfVxufVxuXG5AbWl4aW4gYnV0dG9uLW91dGxpbmUtdmFyaWFudCgkY29sb3I6ICRwcmltYXJ5LWNvbG9yKSB7XG4gIGJhY2tncm91bmQ6ICRsaWdodC1jb2xvcjtcbiAgYm9yZGVyLWNvbG9yOiAkY29sb3I7XG4gIGNvbG9yOiAkY29sb3I7XG4gICY6Zm9jdXMge1xuICAgIEBpbmNsdWRlIGNvbnRyb2wtc2hhZG93KCRjb2xvcik7XG4gIH1cbiAgJjpmb2N1cyxcbiAgJjpob3ZlciB7XG4gICAgYmFja2dyb3VuZDogbGlnaHRlbigkY29sb3IsIDUwJSk7XG4gICAgYm9yZGVyLWNvbG9yOiBkYXJrZW4oJGNvbG9yLCAyJSk7XG4gICAgY29sb3I6ICRjb2xvcjtcbiAgfVxuICAmOmFjdGl2ZSxcbiAgJi5hY3RpdmUge1xuICAgIGJhY2tncm91bmQ6ICRjb2xvcjtcbiAgICBib3JkZXItY29sb3I6IGRhcmtlbigkY29sb3IsIDUlKTtcbiAgICBjb2xvcjogJGxpZ2h0LWNvbG9yO1xuICB9XG4gICYubG9hZGluZyB7XG4gICAgJjo6YWZ0ZXIge1xuICAgICAgYm9yZGVyLWJvdHRvbS1jb2xvcjogJGNvbG9yO1xuICAgICAgYm9yZGVyLWxlZnQtY29sb3I6ICRjb2xvcjtcbiAgICB9XG4gIH1cbn1cbiIsIi8vIENsZWFyZml4IG1peGluXG5AbWl4aW4gY2xlYXJmaXgoKSB7XG4gICY6OmFmdGVyIHtcbiAgICBjbGVhcjogYm90aDtcbiAgICBjb250ZW50OiBcIlwiO1xuICAgIGRpc3BsYXk6IHRhYmxlO1xuICB9XG59XG4iLCIvLyBCYWNrZ3JvdW5kIGNvbG9yIHV0aWxpdHkgbWl4aW5cbkBtaXhpbiBiZy1jb2xvci12YXJpYW50KCRuYW1lOiBcIi5iZy1wcmltYXJ5XCIsICRjb2xvcjogJHByaW1hcnktY29sb3IpIHtcbiAgI3skbmFtZX0ge1xuICAgIGJhY2tncm91bmQ6ICRjb2xvciAhaW1wb3J0YW50O1xuXG4gICAgQGlmIChsaWdodG5lc3MoJGNvbG9yKSA8IDYwKSB7XG4gICAgICBjb2xvcjogJGxpZ2h0LWNvbG9yO1xuICAgIH1cbiAgfVxufVxuXG4vLyBUZXh0IGNvbG9yIHV0aWxpdHkgbWl4aW5cbkBtaXhpbiB0ZXh0LWNvbG9yLXZhcmlhbnQoJG5hbWU6IFwiLnRleHQtcHJpbWFyeVwiLCAkY29sb3I6ICRwcmltYXJ5LWNvbG9yKSB7XG4gICN7JG5hbWV9IHtcbiAgICBjb2xvcjogJGNvbG9yICFpbXBvcnRhbnQ7XG4gIH1cblxuICBhI3skbmFtZX0ge1xuICAgICY6Zm9jdXMsXG4gICAgJjpob3ZlciB7XG4gICAgICBjb2xvcjogZGFya2VuKCRjb2xvciwgNSUpO1xuICAgIH1cbiAgICAmOnZpc2l0ZWQge1xuICAgICAgY29sb3I6IGxpZ2h0ZW4oJGNvbG9yLCA1JSk7XG4gICAgfVxuICB9XG59XG4iLCIvLyBMYWJlbCBiYXNlIHN0eWxlXG5AbWl4aW4gbGFiZWwtYmFzZSgpIHtcbiAgYm9yZGVyLXJhZGl1czogJGJvcmRlci1yYWRpdXM7XG4gIGxpbmUtaGVpZ2h0OiAxLjI1O1xuICBwYWRkaW5nOiAuMXJlbSAuMnJlbTtcbn1cblxuQG1peGluIGxhYmVsLXZhcmlhbnQoJGNvbG9yOiAkbGlnaHQtY29sb3IsICRiZy1jb2xvcjogJHByaW1hcnktY29sb3IpIHtcbiAgYmFja2dyb3VuZDogJGJnLWNvbG9yO1xuICBjb2xvcjogJGNvbG9yO1xufVxuIiwiLy8gTWFyZ2luIHV0aWxpdHkgbWl4aW5cbkBtaXhpbiBtYXJnaW4tdmFyaWFudCgkaWQ6IDEsICRzaXplOiAkdW5pdC0xKSB7XG4gIC5tLSN7JGlkfSB7XG4gICAgbWFyZ2luOiAkc2l6ZSAhaW1wb3J0YW50O1xuICB9XG5cbiAgLm1iLSN7JGlkfSB7XG4gICAgbWFyZ2luLWJvdHRvbTogJHNpemUgIWltcG9ydGFudDtcbiAgfVxuXG4gIC5tbC0jeyRpZH0ge1xuICAgIG1hcmdpbi1sZWZ0OiAkc2l6ZSAhaW1wb3J0YW50O1xuICB9XG5cbiAgLm1yLSN7JGlkfSB7XG4gICAgbWFyZ2luLXJpZ2h0OiAkc2l6ZSAhaW1wb3J0YW50O1xuICB9XG5cbiAgLm10LSN7JGlkfSB7XG4gICAgbWFyZ2luLXRvcDogJHNpemUgIWltcG9ydGFudDtcbiAgfVxuXG4gIC5teC0jeyRpZH0ge1xuICAgIG1hcmdpbi1sZWZ0OiAkc2l6ZSAhaW1wb3J0YW50O1xuICAgIG1hcmdpbi1yaWdodDogJHNpemUgIWltcG9ydGFudDtcbiAgfVxuXG4gIC5teS0jeyRpZH0ge1xuICAgIG1hcmdpbi1ib3R0b206ICRzaXplICFpbXBvcnRhbnQ7XG4gICAgbWFyZ2luLXRvcDogJHNpemUgIWltcG9ydGFudDtcbiAgfVxufVxuXG4vLyBQYWRkaW5nIHV0aWxpdHkgbWl4aW5cbkBtaXhpbiBwYWRkaW5nLXZhcmlhbnQoJGlkOiAxLCAkc2l6ZTogJHVuaXQtMSkge1xuICAucC0jeyRpZH0ge1xuICAgIHBhZGRpbmc6ICRzaXplICFpbXBvcnRhbnQ7XG4gIH1cblxuICAucGItI3skaWR9IHtcbiAgICBwYWRkaW5nLWJvdHRvbTogJHNpemUgIWltcG9ydGFudDtcbiAgfVxuXG4gIC5wbC0jeyRpZH0ge1xuICAgIHBhZGRpbmctbGVmdDogJHNpemUgIWltcG9ydGFudDtcbiAgfVxuXG4gIC5wci0jeyRpZH0ge1xuICAgIHBhZGRpbmctcmlnaHQ6ICRzaXplICFpbXBvcnRhbnQ7XG4gIH1cblxuICAucHQtI3skaWR9IHtcbiAgICBwYWRkaW5nLXRvcDogJHNpemUgIWltcG9ydGFudDtcbiAgfVxuXG4gIC5weC0jeyRpZH0ge1xuICAgIHBhZGRpbmctbGVmdDogJHNpemUgIWltcG9ydGFudDtcbiAgICBwYWRkaW5nLXJpZ2h0OiAkc2l6ZSAhaW1wb3J0YW50O1xuICB9XG4gIFxuICAucHktI3skaWR9IHtcbiAgICBwYWRkaW5nLWJvdHRvbTogJHNpemUgIWltcG9ydGFudDtcbiAgICBwYWRkaW5nLXRvcDogJHNpemUgIWltcG9ydGFudDtcbiAgfVxufVxuIiwiLy8gQ29tcG9uZW50IGZvY3VzIHNoYWRvd1xuQG1peGluIGNvbnRyb2wtc2hhZG93KCRjb2xvcjogJHByaW1hcnktY29sb3IpIHtcbiAgYm94LXNoYWRvdzogMCAwIDAgLjFyZW0gcmdiYSgkY29sb3IsIC4yKTtcbn1cblxuLy8gU2hhZG93IG1peGluXG5AbWl4aW4gc2hhZG93LXZhcmlhbnQoJG9mZnNldCkge1xuICBib3gtc2hhZG93OiAwICRvZmZzZXQgKCRvZmZzZXQgKyAuMDVyZW0pICogMiByZ2JhKCRkYXJrLWNvbG9yLCAuMyk7XG59XG4iLCIvLyBUZXh0IEVsbGlwc2lzXG5AbWl4aW4gdGV4dC1lbGxpcHNpcygpIHtcbiAgb3ZlcmZsb3c6IGhpZGRlbjtcbiAgdGV4dC1vdmVyZmxvdzogZWxsaXBzaXM7XG4gIHdoaXRlLXNwYWNlOiBub3dyYXA7XG59XG4iLCIvLyBUb2FzdCB2YXJpYW50IG1peGluXG5AbWl4aW4gdG9hc3QtdmFyaWFudCgkY29sb3I6ICRkYXJrLWNvbG9yKSB7XG4gIGJhY2tncm91bmQ6IHJnYmEoJGNvbG9yLCAuOTUpO1xuICBib3JkZXItY29sb3I6ICRjb2xvcjtcbn1cbiIsIi8qIE1hbnVhbGx5IGZvcmtlZCBmcm9tIE5vcm1hbGl6ZS5jc3MgKi9cbi8qIG5vcm1hbGl6ZS5jc3MgdjUuMC4wIHwgTUlUIExpY2Vuc2UgfCBnaXRodWIuY29tL25lY29sYXMvbm9ybWFsaXplLmNzcyAqL1xuXG4vKipcbiAqIDEuIENoYW5nZSB0aGUgZGVmYXVsdCBmb250IGZhbWlseSBpbiBhbGwgYnJvd3NlcnMgKG9waW5pb25hdGVkKS5cbiAqIDIuIENvcnJlY3QgdGhlIGxpbmUgaGVpZ2h0IGluIGFsbCBicm93c2Vycy5cbiAqIDMuIFByZXZlbnQgYWRqdXN0bWVudHMgb2YgZm9udCBzaXplIGFmdGVyIG9yaWVudGF0aW9uIGNoYW5nZXMgaW5cbiAqICAgIElFIG9uIFdpbmRvd3MgUGhvbmUgYW5kIGluIGlPUy5cbiAqL1xuXG4vKiBEb2N1bWVudFxuICAgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0gKi9cblxuaHRtbCB7XG4gIGZvbnQtZmFtaWx5OiBzYW5zLXNlcmlmOyAvKiAxICovXG4gIC1tcy10ZXh0LXNpemUtYWRqdXN0OiAxMDAlOyAvKiAzICovXG4gIC13ZWJraXQtdGV4dC1zaXplLWFkanVzdDogMTAwJTsgLyogMyAqL1xufVxuXG4vKiBTZWN0aW9uc1xuICAgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0gKi9cblxuLyoqXG4gKiBSZW1vdmUgdGhlIG1hcmdpbiBpbiBhbGwgYnJvd3NlcnMgKG9waW5pb25hdGVkKS5cbiAqL1xuXG5ib2R5IHtcbiAgbWFyZ2luOiAwO1xufVxuXG4vKipcbiAqIEFkZCB0aGUgY29ycmVjdCBkaXNwbGF5IGluIElFIDktLlxuICovXG5cbmFydGljbGUsXG5hc2lkZSxcbmZvb3RlcixcbmhlYWRlcixcbm5hdixcbnNlY3Rpb24ge1xuICBkaXNwbGF5OiBibG9jaztcbn1cblxuLyoqXG4gKiBDb3JyZWN0IHRoZSBmb250IHNpemUgYW5kIG1hcmdpbiBvbiBgaDFgIGVsZW1lbnRzIHdpdGhpbiBgc2VjdGlvbmAgYW5kXG4gKiBgYXJ0aWNsZWAgY29udGV4dHMgaW4gQ2hyb21lLCBGaXJlZm94LCBhbmQgU2FmYXJpLlxuICovXG5cbmgxIHtcbiAgZm9udC1zaXplOiAyZW07XG4gIG1hcmdpbjogMC42N2VtIDA7XG59XG5cbi8qIEdyb3VwaW5nIGNvbnRlbnRcbiAgID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09ICovXG5cbi8qKlxuICogQWRkIHRoZSBjb3JyZWN0IGRpc3BsYXkgaW4gSUUgOS0uXG4gKiAxLiBBZGQgdGhlIGNvcnJlY3QgZGlzcGxheSBpbiBJRS5cbiAqL1xuXG5maWdjYXB0aW9uLFxuZmlndXJlLFxubWFpbiB7IC8qIDEgKi9cbiAgZGlzcGxheTogYmxvY2s7XG59XG5cbi8qKlxuICogQWRkIHRoZSBjb3JyZWN0IG1hcmdpbiBpbiBJRSA4IChyZW1vdmVkKS5cbiAqL1xuXG4vKipcbiAqIDEuIEFkZCB0aGUgY29ycmVjdCBib3ggc2l6aW5nIGluIEZpcmVmb3guXG4gKiAyLiBTaG93IHRoZSBvdmVyZmxvdyBpbiBFZGdlIGFuZCBJRS5cbiAqL1xuXG5ociB7XG4gIGJveC1zaXppbmc6IGNvbnRlbnQtYm94OyAvKiAxICovXG4gIGhlaWdodDogMDsgLyogMSAqL1xuICBvdmVyZmxvdzogdmlzaWJsZTsgLyogMiAqL1xufVxuXG4vKipcbiAqIDEuIENvcnJlY3QgdGhlIGluaGVyaXRhbmNlIGFuZCBzY2FsaW5nIG9mIGZvbnQgc2l6ZSBpbiBhbGwgYnJvd3NlcnMuIChyZW1vdmVkKVxuICogMi4gQ29ycmVjdCB0aGUgb2RkIGBlbWAgZm9udCBzaXppbmcgaW4gYWxsIGJyb3dzZXJzLlxuICovXG5cbi8qIFRleHQtbGV2ZWwgc2VtYW50aWNzXG4gICA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PSAqL1xuXG4vKipcbiAqIDEuIFJlbW92ZSB0aGUgZ3JheSBiYWNrZ3JvdW5kIG9uIGFjdGl2ZSBsaW5rcyBpbiBJRSAxMC5cbiAqIDIuIFJlbW92ZSBnYXBzIGluIGxpbmtzIHVuZGVybGluZSBpbiBpT1MgOCsgYW5kIFNhZmFyaSA4Ky5cbiAqL1xuXG5hIHtcbiAgYmFja2dyb3VuZC1jb2xvcjogdHJhbnNwYXJlbnQ7IC8qIDEgKi9cbiAgLXdlYmtpdC10ZXh0LWRlY29yYXRpb24tc2tpcDogb2JqZWN0czsgLyogMiAqL1xufVxuXG4vKipcbiAqIFJlbW92ZSB0aGUgb3V0bGluZSBvbiBmb2N1c2VkIGxpbmtzIHdoZW4gdGhleSBhcmUgYWxzbyBhY3RpdmUgb3IgaG92ZXJlZFxuICogaW4gYWxsIGJyb3dzZXJzIChvcGluaW9uYXRlZCkuXG4gKi9cblxuYTphY3RpdmUsXG5hOmhvdmVyIHtcbiAgb3V0bGluZS13aWR0aDogMDtcbn1cblxuLyoqXG4gKiBNb2RpZnkgZGVmYXVsdCBzdHlsaW5nIG9mIGFkZHJlc3MuXG4gKi9cblxuYWRkcmVzcyB7XG4gIGZvbnQtc3R5bGU6IG5vcm1hbDtcbn1cblxuLyoqXG4gKiAxLiBSZW1vdmUgdGhlIGJvdHRvbSBib3JkZXIgaW4gRmlyZWZveCAzOS0uXG4gKiAyLiBBZGQgdGhlIGNvcnJlY3QgdGV4dCBkZWNvcmF0aW9uIGluIENocm9tZSwgRWRnZSwgSUUsIE9wZXJhLCBhbmQgU2FmYXJpLiAocmVtb3ZlZClcbiAqL1xuXG4vKipcbiAqIFByZXZlbnQgdGhlIGR1cGxpY2F0ZSBhcHBsaWNhdGlvbiBvZiBgYm9sZGVyYCBieSB0aGUgbmV4dCBydWxlIGluIFNhZmFyaSA2LlxuICovXG5cbmIsXG5zdHJvbmcge1xuICBmb250LXdlaWdodDogaW5oZXJpdDtcbn1cblxuLyoqXG4gKiBBZGQgdGhlIGNvcnJlY3QgZm9udCB3ZWlnaHQgaW4gQ2hyb21lLCBFZGdlLCBhbmQgU2FmYXJpLlxuICovXG5cbmIsXG5zdHJvbmcge1xuICBmb250LXdlaWdodDogYm9sZGVyO1xufVxuXG4vKipcbiAqIDEuIENvcnJlY3QgdGhlIGluaGVyaXRhbmNlIGFuZCBzY2FsaW5nIG9mIGZvbnQgc2l6ZSBpbiBhbGwgYnJvd3NlcnMuXG4gKiAyLiBDb3JyZWN0IHRoZSBvZGQgYGVtYCBmb250IHNpemluZyBpbiBhbGwgYnJvd3NlcnMuXG4gKi9cblxuY29kZSxcbmtiZCxcbnByZSxcbnNhbXAge1xuICBmb250LWZhbWlseTogJG1vbm8tZm9udC1mYW1pbHk7IC8qIDEgKGNoYW5nZWQpICovXG4gIGZvbnQtc2l6ZTogMWVtOyAvKiAyICovXG59XG5cbi8qKlxuICogQWRkIHRoZSBjb3JyZWN0IGZvbnQgc3R5bGUgaW4gQW5kcm9pZCA0LjMtLlxuICovXG5cbmRmbiB7XG4gIGZvbnQtc3R5bGU6IGl0YWxpYztcbn1cblxuLyoqXG4gKiBBZGQgdGhlIGNvcnJlY3QgYmFja2dyb3VuZCBhbmQgY29sb3IgaW4gSUUgOS0uIChSZW1vdmVkKVxuICovXG5cbi8qKlxuICogQWRkIHRoZSBjb3JyZWN0IGZvbnQgc2l6ZSBpbiBhbGwgYnJvd3NlcnMuXG4gKi9cblxuc21hbGwge1xuICBmb250LXNpemU6IDgwJTtcbiAgZm9udC13ZWlnaHQ6IDQwMDsgLyogKGFkZGVkKSAqL1xufVxuXG4vKipcbiAqIFByZXZlbnQgYHN1YmAgYW5kIGBzdXBgIGVsZW1lbnRzIGZyb20gYWZmZWN0aW5nIHRoZSBsaW5lIGhlaWdodCBpblxuICogYWxsIGJyb3dzZXJzLlxuICovXG5cbnN1YixcbnN1cCB7XG4gIGZvbnQtc2l6ZTogNzUlO1xuICBsaW5lLWhlaWdodDogMDtcbiAgcG9zaXRpb246IHJlbGF0aXZlO1xuICB2ZXJ0aWNhbC1hbGlnbjogYmFzZWxpbmU7XG59XG5cbnN1YiB7XG4gIGJvdHRvbTogLTAuMjVlbTtcbn1cblxuc3VwIHtcbiAgdG9wOiAtMC41ZW07XG59XG5cbi8qIEVtYmVkZGVkIGNvbnRlbnRcbiAgID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09ICovXG5cbi8qKlxuICogQWRkIHRoZSBjb3JyZWN0IGRpc3BsYXkgaW4gSUUgOS0uXG4gKi9cblxuYXVkaW8sXG52aWRlbyB7XG4gIGRpc3BsYXk6IGlubGluZS1ibG9jaztcbn1cblxuLyoqXG4gKiBBZGQgdGhlIGNvcnJlY3QgZGlzcGxheSBpbiBpT1MgNC03LlxuICovXG5cbmF1ZGlvOm5vdChbY29udHJvbHNdKSB7XG4gIGRpc3BsYXk6IG5vbmU7XG4gIGhlaWdodDogMDtcbn1cblxuLyoqXG4gKiBSZW1vdmUgdGhlIGJvcmRlciBvbiBpbWFnZXMgaW5zaWRlIGxpbmtzIGluIElFIDEwLS5cbiAqL1xuXG5pbWcge1xuICBib3JkZXItc3R5bGU6IG5vbmU7XG59XG5cbi8qKlxuICogSGlkZSB0aGUgb3ZlcmZsb3cgaW4gSUUuXG4gKi9cblxuc3ZnOm5vdCg6cm9vdCkge1xuICBvdmVyZmxvdzogaGlkZGVuO1xufVxuXG4vKiBGb3Jtc1xuICAgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0gKi9cblxuLyoqXG4gKiAxLiBDaGFuZ2UgdGhlIGZvbnQgc3R5bGVzIGluIGFsbCBicm93c2VycyAob3BpbmlvbmF0ZWQpLlxuICogMi4gUmVtb3ZlIHRoZSBtYXJnaW4gaW4gRmlyZWZveCBhbmQgU2FmYXJpLlxuICovXG5cbmJ1dHRvbixcbmlucHV0LFxub3B0Z3JvdXAsXG5zZWxlY3QsXG50ZXh0YXJlYSB7XG4gIGZvbnQtZmFtaWx5OiBpbmhlcml0OyAvKiAxIChjaGFuZ2VkKSAqL1xuICBmb250LXNpemU6IGluaGVyaXQ7IC8qIDEgKGNoYW5nZWQpICovXG4gIGxpbmUtaGVpZ2h0OiBpbmhlcml0OyAvKiAxIChjaGFuZ2VkKSAqL1xuICBtYXJnaW46IDA7IC8qIDIgKi9cbn1cblxuLyoqXG4gKiBTaG93IHRoZSBvdmVyZmxvdyBpbiBJRS5cbiAqIDEuIFNob3cgdGhlIG92ZXJmbG93IGluIEVkZ2UuXG4gKi9cblxuYnV0dG9uLFxuaW5wdXQgeyAvKiAxICovXG4gIG92ZXJmbG93OiB2aXNpYmxlO1xufVxuXG4vKipcbiAqIFJlbW92ZSB0aGUgaW5oZXJpdGFuY2Ugb2YgdGV4dCB0cmFuc2Zvcm0gaW4gRWRnZSwgRmlyZWZveCwgYW5kIElFLlxuICogMS4gUmVtb3ZlIHRoZSBpbmhlcml0YW5jZSBvZiB0ZXh0IHRyYW5zZm9ybSBpbiBGaXJlZm94LlxuICovXG5cbmJ1dHRvbixcbnNlbGVjdCB7IC8qIDEgKi9cbiAgdGV4dC10cmFuc2Zvcm06IG5vbmU7XG59XG5cbi8qKlxuICogMS4gUHJldmVudCBhIFdlYktpdCBidWcgd2hlcmUgKDIpIGRlc3Ryb3lzIG5hdGl2ZSBgYXVkaW9gIGFuZCBgdmlkZW9gXG4gKiAgICBjb250cm9scyBpbiBBbmRyb2lkIDQuXG4gKiAyLiBDb3JyZWN0IHRoZSBpbmFiaWxpdHkgdG8gc3R5bGUgY2xpY2thYmxlIHR5cGVzIGluIGlPUyBhbmQgU2FmYXJpLlxuICovXG5cbmJ1dHRvbixcbmh0bWwgW3R5cGU9XCJidXR0b25cIl0sIC8qIDEgKi9cblt0eXBlPVwicmVzZXRcIl0sXG5bdHlwZT1cInN1Ym1pdFwiXSB7XG4gIC13ZWJraXQtYXBwZWFyYW5jZTogYnV0dG9uOyAvKiAyICovXG59XG5cbi8qKlxuICogUmVtb3ZlIHRoZSBpbm5lciBib3JkZXIgYW5kIHBhZGRpbmcgaW4gRmlyZWZveC5cbiAqL1xuXG5idXR0b246Oi1tb3otZm9jdXMtaW5uZXIsXG5bdHlwZT1cImJ1dHRvblwiXTo6LW1vei1mb2N1cy1pbm5lcixcblt0eXBlPVwicmVzZXRcIl06Oi1tb3otZm9jdXMtaW5uZXIsXG5bdHlwZT1cInN1Ym1pdFwiXTo6LW1vei1mb2N1cy1pbm5lciB7XG4gIGJvcmRlci1zdHlsZTogbm9uZTtcbiAgcGFkZGluZzogMDtcbn1cblxuLyoqXG4gKiBSZXN0b3JlIHRoZSBmb2N1cyBzdHlsZXMgdW5zZXQgYnkgdGhlIHByZXZpb3VzIHJ1bGUgKHJlbW92ZWQpLlxuICovXG5cblxuLyoqXG4gKiBDaGFuZ2UgdGhlIGJvcmRlciwgbWFyZ2luLCBhbmQgcGFkZGluZyBpbiBhbGwgYnJvd3NlcnMgKG9waW5pb25hdGVkKSAoY2hhbmdlZCkuXG4gKi9cblxuZmllbGRzZXQge1xuICBib3JkZXI6IDA7XG4gIG1hcmdpbjogMDtcbiAgcGFkZGluZzogMDtcbn1cblxuLyoqXG4gKiAxLiBDb3JyZWN0IHRoZSB0ZXh0IHdyYXBwaW5nIGluIEVkZ2UgYW5kIElFLlxuICogMi4gQ29ycmVjdCB0aGUgY29sb3IgaW5oZXJpdGFuY2UgZnJvbSBgZmllbGRzZXRgIGVsZW1lbnRzIGluIElFLlxuICogMy4gUmVtb3ZlIHRoZSBwYWRkaW5nIHNvIGRldmVsb3BlcnMgYXJlIG5vdCBjYXVnaHQgb3V0IHdoZW4gdGhleSB6ZXJvIG91dFxuICogICAgYGZpZWxkc2V0YCBlbGVtZW50cyBpbiBhbGwgYnJvd3NlcnMuXG4gKi9cblxubGVnZW5kIHtcbiAgYm94LXNpemluZzogYm9yZGVyLWJveDsgLyogMSAqL1xuICBjb2xvcjogaW5oZXJpdDsgLyogMiAqL1xuICBkaXNwbGF5OiB0YWJsZTsgLyogMSAqL1xuICBtYXgtd2lkdGg6IDEwMCU7IC8qIDEgKi9cbiAgcGFkZGluZzogMDsgLyogMyAqL1xuICB3aGl0ZS1zcGFjZTogbm9ybWFsOyAvKiAxICovXG59XG5cbi8qKlxuICogMS4gQWRkIHRoZSBjb3JyZWN0IGRpc3BsYXkgaW4gSUUgOS0uXG4gKiAyLiBBZGQgdGhlIGNvcnJlY3QgdmVydGljYWwgYWxpZ25tZW50IGluIENocm9tZSwgRmlyZWZveCwgYW5kIE9wZXJhLlxuICovXG5cbnByb2dyZXNzIHtcbiAgZGlzcGxheTogaW5saW5lLWJsb2NrOyAvKiAxICovXG4gIHZlcnRpY2FsLWFsaWduOiBiYXNlbGluZTsgLyogMiAqL1xufVxuXG4vKipcbiAqIFJlbW92ZSB0aGUgZGVmYXVsdCB2ZXJ0aWNhbCBzY3JvbGxiYXIgaW4gSUUuXG4gKi9cblxudGV4dGFyZWEge1xuICBvdmVyZmxvdzogYXV0bztcbn1cblxuLyoqXG4gKiAxLiBBZGQgdGhlIGNvcnJlY3QgYm94IHNpemluZyBpbiBJRSAxMC0uXG4gKiAyLiBSZW1vdmUgdGhlIHBhZGRpbmcgaW4gSUUgMTAtLlxuICovXG5cblt0eXBlPVwiY2hlY2tib3hcIl0sXG5bdHlwZT1cInJhZGlvXCJdIHtcbiAgYm94LXNpemluZzogYm9yZGVyLWJveDsgLyogMSAqL1xuICBwYWRkaW5nOiAwOyAvKiAyICovXG59XG5cbi8qKlxuICogQ29ycmVjdCB0aGUgY3Vyc29yIHN0eWxlIG9mIGluY3JlbWVudCBhbmQgZGVjcmVtZW50IGJ1dHRvbnMgaW4gQ2hyb21lLlxuICovXG5cblt0eXBlPVwibnVtYmVyXCJdOjotd2Via2l0LWlubmVyLXNwaW4tYnV0dG9uLFxuW3R5cGU9XCJudW1iZXJcIl06Oi13ZWJraXQtb3V0ZXItc3Bpbi1idXR0b24ge1xuICBoZWlnaHQ6IGF1dG87XG59XG5cbi8qKlxuICogMS4gQ29ycmVjdCB0aGUgb2RkIGFwcGVhcmFuY2UgaW4gQ2hyb21lIGFuZCBTYWZhcmkuXG4gKiAyLiBDb3JyZWN0IHRoZSBvdXRsaW5lIHN0eWxlIGluIFNhZmFyaS5cbiAqL1xuXG5bdHlwZT1cInNlYXJjaFwiXSB7XG4gIC13ZWJraXQtYXBwZWFyYW5jZTogdGV4dGZpZWxkOyAvKiAxICovXG4gIG91dGxpbmUtb2Zmc2V0OiAtMnB4OyAvKiAyICovXG59XG5cbi8qKlxuICogUmVtb3ZlIHRoZSBpbm5lciBwYWRkaW5nIGFuZCBjYW5jZWwgYnV0dG9ucyBpbiBDaHJvbWUgYW5kIFNhZmFyaSBvbiBtYWNPUy5cbiAqL1xuXG5bdHlwZT1cInNlYXJjaFwiXTo6LXdlYmtpdC1zZWFyY2gtY2FuY2VsLWJ1dHRvbixcblt0eXBlPVwic2VhcmNoXCJdOjotd2Via2l0LXNlYXJjaC1kZWNvcmF0aW9uIHtcbiAgLXdlYmtpdC1hcHBlYXJhbmNlOiBub25lO1xufVxuXG4vKipcbiAqIDEuIENvcnJlY3QgdGhlIGluYWJpbGl0eSB0byBzdHlsZSBjbGlja2FibGUgdHlwZXMgaW4gaU9TIGFuZCBTYWZhcmkuXG4gKiAyLiBDaGFuZ2UgZm9udCBwcm9wZXJ0aWVzIHRvIGBpbmhlcml0YCBpbiBTYWZhcmkuXG4gKi9cblxuOjotd2Via2l0LWZpbGUtdXBsb2FkLWJ1dHRvbiB7XG4gIC13ZWJraXQtYXBwZWFyYW5jZTogYnV0dG9uOyAvKiAxICovXG4gIGZvbnQ6IGluaGVyaXQ7IC8qIDIgKi9cbn1cblxuLyogSW50ZXJhY3RpdmVcbiAgID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09ICovXG5cbi8qXG4gKiBBZGQgdGhlIGNvcnJlY3QgZGlzcGxheSBpbiBJRSA5LS5cbiAqIDEuIEFkZCB0aGUgY29ycmVjdCBkaXNwbGF5IGluIEVkZ2UsIElFLCBhbmQgRmlyZWZveC5cbiAqL1xuXG5kZXRhaWxzLCAvKiAxICovXG5tZW51IHtcbiAgZGlzcGxheTogYmxvY2s7XG59XG5cbi8qXG4gKiBBZGQgdGhlIGNvcnJlY3QgZGlzcGxheSBpbiBhbGwgYnJvd3NlcnMuXG4gKi9cblxuc3VtbWFyeSB7XG4gIGRpc3BsYXk6IGxpc3QtaXRlbTtcbiAgb3V0bGluZTogbm9uZTtcbn1cblxuLyogU2NyaXB0aW5nXG4gICA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PSAqL1xuXG4vKipcbiAqIEFkZCB0aGUgY29ycmVjdCBkaXNwbGF5IGluIElFIDktLlxuICovXG5cbmNhbnZhcyB7XG4gIGRpc3BsYXk6IGlubGluZS1ibG9jaztcbn1cblxuLyoqXG4gKiBBZGQgdGhlIGNvcnJlY3QgZGlzcGxheSBpbiBJRS5cbiAqL1xuXG50ZW1wbGF0ZSB7XG4gIGRpc3BsYXk6IG5vbmU7XG59XG5cbi8qIEhpZGRlblxuICAgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0gKi9cblxuLyoqXG4gKiBBZGQgdGhlIGNvcnJlY3QgZGlzcGxheSBpbiBJRSAxMC0uXG4gKi9cblxuW2hpZGRlbl0ge1xuICBkaXNwbGF5OiBub25lO1xufVxuIiwiLy8gQmFzZVxuKixcbio6OmJlZm9yZSxcbio6OmFmdGVyIHtcbiAgYm94LXNpemluZzogaW5oZXJpdDtcbn1cblxuaHRtbCB7XG4gIGJveC1zaXppbmc6IGJvcmRlci1ib3g7XG4gIGZvbnQtc2l6ZTogJGh0bWwtZm9udC1zaXplO1xuICBsaW5lLWhlaWdodDogJGh0bWwtbGluZS1oZWlnaHQ7XG4gIC13ZWJraXQtdGFwLWhpZ2hsaWdodC1jb2xvcjogdHJhbnNwYXJlbnQ7XG59XG5cbmJvZHkge1xuICBiYWNrZ3JvdW5kOiAkYm9keS1iZztcbiAgY29sb3I6ICRib2R5LWZvbnQtY29sb3I7XG4gIGZvbnQtZmFtaWx5OiAkYm9keS1mb250LWZhbWlseTtcbiAgZm9udC1zaXplOiAkZm9udC1zaXplO1xuICBvdmVyZmxvdy14OiBoaWRkZW47XG4gIHRleHQtcmVuZGVyaW5nOiBvcHRpbWl6ZUxlZ2liaWxpdHk7XG59XG5cbmEge1xuICBjb2xvcjogJGxpbmstY29sb3I7XG4gIG91dGxpbmU6IG5vbmU7XG4gIHRleHQtZGVjb3JhdGlvbjogbm9uZTtcblxuICAmOmZvY3VzIHtcbiAgICBAaW5jbHVkZSBjb250cm9sLXNoYWRvdygpO1xuICB9XG5cbiAgJjpmb2N1cyxcbiAgJjpob3ZlcixcbiAgJjphY3RpdmUsXG4gICYuYWN0aXZlIHtcbiAgICBjb2xvcjogJGxpbmstY29sb3ItZGFyaztcbiAgICB0ZXh0LWRlY29yYXRpb246IHVuZGVybGluZTtcbiAgfVxuXG4gICY6dmlzaXRlZCB7XG4gICAgY29sb3I6ICRsaW5rLWNvbG9yLWxpZ2h0O1xuICB9XG59XG4iLCIvLyBUeXBvZ3JhcGh5XG4vLyBIZWFkaW5nc1xuaDEsXG5oMixcbmgzLFxuaDQsXG5oNSxcbmg2IHtcbiAgY29sb3I6IGluaGVyaXQ7XG4gIGZvbnQtd2VpZ2h0OiA1MDA7XG4gIGxpbmUtaGVpZ2h0OiAxLjI7XG4gIG1hcmdpbi1ib3R0b206IC41ZW07XG4gIG1hcmdpbi10b3A6IDA7XG59XG4uaDEsXG4uaDIsXG4uaDMsXG4uaDQsXG4uaDUsXG4uaDYge1xuICBmb250LXdlaWdodDogNTAwO1xufVxuaDEsXG4uaDEge1xuICBmb250LXNpemU6IDJyZW07XG59XG5oMixcbi5oMiB7XG4gIGZvbnQtc2l6ZTogMS42cmVtO1xufVxuaDMsXG4uaDMge1xuICBmb250LXNpemU6IDEuNHJlbTtcbn1cbmg0LFxuLmg0IHtcbiAgZm9udC1zaXplOiAxLjJyZW07XG59XG5oNSxcbi5oNSB7XG4gIGZvbnQtc2l6ZTogMXJlbTtcbn1cbmg2LFxuLmg2IHtcbiAgZm9udC1zaXplOiAuOHJlbTtcbn1cblxuLy8gUGFyYWdyYXBoc1xucCB7XG4gIG1hcmdpbjogMCAwICRsaW5lLWhlaWdodDtcbn1cblxuLy8gU2VtYW50aWMgdGV4dCBlbGVtZW50c1xuYSxcbmlucyxcbnUge1xuICB0ZXh0LWRlY29yYXRpb24tc2tpcDogaW5rIGVkZ2VzO1xufVxuXG5hYmJyW3RpdGxlXSB7XG4gIGJvcmRlci1ib3R0b206ICRib3JkZXItd2lkdGggZG90dGVkO1xuICBjdXJzb3I6IGhlbHA7XG4gIHRleHQtZGVjb3JhdGlvbjogbm9uZTtcbn1cblxua2JkIHtcbiAgQGluY2x1ZGUgbGFiZWwtYmFzZSgpO1xuICBAaW5jbHVkZSBsYWJlbC12YXJpYW50KCRsaWdodC1jb2xvciwgJGRhcmstY29sb3IpO1xuICBmb250LXNpemU6ICRmb250LXNpemUtc207XG59XG5cbm1hcmsge1xuICBAaW5jbHVkZSBsYWJlbC12YXJpYW50KCRib2R5LWZvbnQtY29sb3IsICRoaWdobGlnaHQtY29sb3IpO1xuICBib3JkZXItYm90dG9tOiAkdW5pdC1vIHNvbGlkIGRhcmtlbigkaGlnaGxpZ2h0LWNvbG9yLCAxNSUpO1xuICBib3JkZXItcmFkaXVzOiAkYm9yZGVyLXJhZGl1cztcbiAgcGFkZGluZzogJHVuaXQtbyAkdW5pdC1oIDA7XG59XG5cbi8vIEJsb2NrcXVvdGVcbmJsb2NrcXVvdGUge1xuICBib3JkZXItbGVmdDogJGJvcmRlci13aWR0aC1sZyBzb2xpZCAkYm9yZGVyLWNvbG9yO1xuICBtYXJnaW4tbGVmdDogMDtcbiAgcGFkZGluZzogJHVuaXQtMiAkdW5pdC00O1xuXG4gIHA6bGFzdC1jaGlsZCB7XG4gICAgbWFyZ2luLWJvdHRvbTogMDtcbiAgfVxufVxuXG4vLyBMaXN0c1xudWwsXG5vbCB7XG4gIG1hcmdpbjogJHVuaXQtNCAwICR1bml0LTQgJHVuaXQtNDtcbiAgcGFkZGluZzogMDtcblxuICB1bCxcbiAgb2wge1xuICAgIG1hcmdpbjogJHVuaXQtNCAwICR1bml0LTQgJHVuaXQtNDtcbiAgfVxuXG4gIGxpIHtcbiAgICBtYXJnaW4tdG9wOiAkdW5pdC0yO1xuICB9XG59XG5cbnVsIHtcbiAgbGlzdC1zdHlsZTogZGlzYyBpbnNpZGU7XG5cbiAgdWwge1xuICAgIGxpc3Qtc3R5bGUtdHlwZTogY2lyY2xlO1xuICB9XG59XG5cbm9sIHtcbiAgbGlzdC1zdHlsZTogZGVjaW1hbCBpbnNpZGU7XG5cbiAgb2wge1xuICAgIGxpc3Qtc3R5bGUtdHlwZTogbG93ZXItYWxwaGE7XG4gIH1cbn1cblxuZGwge1xuICBkdCB7XG4gICAgZm9udC13ZWlnaHQ6IGJvbGQ7XG4gIH1cbiAgZGQge1xuICAgIG1hcmdpbjogJHVuaXQtMiAwICR1bml0LTQgMDtcbiAgfVxufVxuIiwiLy8gT3B0aW1pemVkIGZvciBFYXN0IEFzaWFuIENKS1xuaHRtbDpsYW5nKHpoKSxcbmh0bWw6bGFuZyh6aC1IYW5zKSxcbi5sYW5nLXpoLFxuLmxhbmctemgtaGFucyB7XG4gIGZvbnQtZmFtaWx5OiAkY2prLXpoLWhhbnMtZm9udC1mYW1pbHk7XG59XG5cbmh0bWw6bGFuZyh6aC1IYW50KSxcbi5sYW5nLXpoLWhhbnQge1xuICBmb250LWZhbWlseTogJGNqay16aC1oYW50LWZvbnQtZmFtaWx5O1xufVxuXG5odG1sOmxhbmcoamEpLFxuLmxhbmctamEge1xuICBmb250LWZhbWlseTogJGNqay1qcC1mb250LWZhbWlseTtcbn1cblxuaHRtbDpsYW5nKGtvKSxcbi5sYW5nLWtvIHtcbiAgZm9udC1mYW1pbHk6ICRjamsta28tZm9udC1mYW1pbHk7XG59XG5cbjpsYW5nKHpoKSxcbjpsYW5nKGphKSxcbi5sYW5nLWNqayB7XG4gIGlucyxcbiAgdSB7XG4gICAgYm9yZGVyLWJvdHRvbTogJGJvcmRlci13aWR0aCBzb2xpZDtcbiAgICB0ZXh0LWRlY29yYXRpb246IG5vbmU7XG4gIH1cblxuICBkZWwgKyBkZWwsXG4gIGRlbCArIHMsXG4gIGlucyArIGlucyxcbiAgaW5zICsgdSxcbiAgcyArIGRlbCxcbiAgcyArIHMsXG4gIHUgKyBpbnMsXG4gIHUgKyB1IHtcbiAgICBtYXJnaW4tbGVmdDogLjEyNWVtO1xuICB9XG59XG4iLCIvLyBUYWJsZXNcbi50YWJsZSB7XG4gIGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2U7XG4gIGJvcmRlci1zcGFjaW5nOiAwO1xuICB3aWR0aDogMTAwJTtcbiAgQGlmICRydGwgPT0gdHJ1ZSB7XG4gICAgdGV4dC1hbGlnbjogcmlnaHQ7XG4gIH0gQGVsc2Uge1xuICAgIHRleHQtYWxpZ246IGxlZnQ7XG4gIH1cblxuICAmLnRhYmxlLXN0cmlwZWQge1xuICAgIHRib2R5IHtcbiAgICAgIHRyOm50aC1vZi10eXBlKG9kZCkge1xuICAgICAgICBiYWNrZ3JvdW5kOiAkYmctY29sb3I7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgJixcbiAgJi50YWJsZS1zdHJpcGVkIHtcbiAgICB0Ym9keSB7XG4gICAgICB0ciB7XG4gICAgICAgICYuYWN0aXZlIHtcbiAgICAgICAgICBiYWNrZ3JvdW5kOiAkYmctY29sb3ItZGFyaztcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gICYudGFibGUtaG92ZXIge1xuICAgIHRib2R5IHtcbiAgICAgIHRyIHtcbiAgICAgICAgJjpob3ZlciB7XG4gICAgICAgICAgYmFja2dyb3VuZDogJGJnLWNvbG9yLWRhcms7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICAvLyBTY29sbGFibGUgdGFibGVzXG4gICYudGFibGUtc2Nyb2xsIHtcbiAgICBkaXNwbGF5OiBibG9jaztcbiAgICBvdmVyZmxvdy14OiBhdXRvO1xuICAgIHBhZGRpbmctYm90dG9tOiAuNzVyZW07XG4gICAgd2hpdGUtc3BhY2U6IG5vd3JhcDtcbiAgfVxuXG4gIHRkLFxuICB0aCB7XG4gICAgYm9yZGVyLWJvdHRvbTogJGJvcmRlci13aWR0aCBzb2xpZCAkYm9yZGVyLWNvbG9yO1xuICAgIHBhZGRpbmc6ICR1bml0LTMgJHVuaXQtMjtcbiAgfVxuICB0aCB7XG4gICAgYm9yZGVyLWJvdHRvbS13aWR0aDogJGJvcmRlci13aWR0aC1sZztcbiAgfVxufVxuIiwiLy8gQnV0dG9uc1xuLmJ0biB7XG4gIGFwcGVhcmFuY2U6IG5vbmU7XG4gIGJhY2tncm91bmQ6ICRiZy1jb2xvci1saWdodDtcbiAgYm9yZGVyOiAkYm9yZGVyLXdpZHRoIHNvbGlkICRwcmltYXJ5LWNvbG9yO1xuICBib3JkZXItcmFkaXVzOiAkYm9yZGVyLXJhZGl1cztcbiAgY29sb3I6ICRwcmltYXJ5LWNvbG9yO1xuICBjdXJzb3I6IHBvaW50ZXI7XG4gIGRpc3BsYXk6IGlubGluZS1ibG9jaztcbiAgZm9udC1zaXplOiAkZm9udC1zaXplO1xuICBoZWlnaHQ6ICRjb250cm9sLXNpemU7XG4gIGxpbmUtaGVpZ2h0OiAkbGluZS1oZWlnaHQ7XG4gIG91dGxpbmU6IG5vbmU7XG4gIHBhZGRpbmc6ICRjb250cm9sLXBhZGRpbmcteSAkY29udHJvbC1wYWRkaW5nLXg7XG4gIHRleHQtYWxpZ246IGNlbnRlcjtcbiAgdGV4dC1kZWNvcmF0aW9uOiBub25lO1xuICB0cmFuc2l0aW9uOiBiYWNrZ3JvdW5kIC4ycywgYm9yZGVyIC4ycywgYm94LXNoYWRvdyAuMnMsIGNvbG9yIC4ycztcbiAgdXNlci1zZWxlY3Q6IG5vbmU7XG4gIHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7XG4gIHdoaXRlLXNwYWNlOiBub3dyYXA7XG4gICY6Zm9jdXMge1xuICAgIEBpbmNsdWRlIGNvbnRyb2wtc2hhZG93KCk7XG4gIH1cbiAgJjpmb2N1cyxcbiAgJjpob3ZlciB7XG4gICAgYmFja2dyb3VuZDogJHNlY29uZGFyeS1jb2xvcjtcbiAgICBib3JkZXItY29sb3I6ICRwcmltYXJ5LWNvbG9yLWRhcms7XG4gICAgdGV4dC1kZWNvcmF0aW9uOiBub25lO1xuICB9XG4gICY6YWN0aXZlLFxuICAmLmFjdGl2ZSB7XG4gICAgYmFja2dyb3VuZDogJHByaW1hcnktY29sb3ItZGFyaztcbiAgICBib3JkZXItY29sb3I6IGRhcmtlbigkcHJpbWFyeS1jb2xvci1kYXJrLCA1JSk7XG4gICAgY29sb3I6ICRsaWdodC1jb2xvcjtcbiAgICB0ZXh0LWRlY29yYXRpb246IG5vbmU7XG4gICAgJi5sb2FkaW5nIHtcbiAgICAgICY6OmFmdGVyIHtcbiAgICAgICAgYm9yZGVyLWJvdHRvbS1jb2xvcjogJGxpZ2h0LWNvbG9yO1xuICAgICAgICBib3JkZXItbGVmdC1jb2xvcjogJGxpZ2h0LWNvbG9yO1xuICAgICAgfVxuICAgIH1cbiAgfVxuICAmW2Rpc2FibGVkXSxcbiAgJjpkaXNhYmxlZCxcbiAgJi5kaXNhYmxlZCB7XG4gICAgY3Vyc29yOiBkZWZhdWx0O1xuICAgIG9wYWNpdHk6IC41O1xuICAgIHBvaW50ZXItZXZlbnRzOiBub25lO1xuICB9XG5cbiAgLy8gQnV0dG9uIFByaW1hcnlcbiAgJi5idG4tcHJpbWFyeSB7XG4gICAgYmFja2dyb3VuZDogJHByaW1hcnktY29sb3I7XG4gICAgYm9yZGVyLWNvbG9yOiAkcHJpbWFyeS1jb2xvci1kYXJrO1xuICAgIGNvbG9yOiAkbGlnaHQtY29sb3I7XG4gICAgJjpmb2N1cyxcbiAgICAmOmhvdmVyIHtcbiAgICAgIGJhY2tncm91bmQ6IGRhcmtlbigkcHJpbWFyeS1jb2xvci1kYXJrLCAyJSk7XG4gICAgICBib3JkZXItY29sb3I6IGRhcmtlbigkcHJpbWFyeS1jb2xvci1kYXJrLCA1JSk7XG4gICAgICBjb2xvcjogJGxpZ2h0LWNvbG9yO1xuICAgIH1cbiAgICAmOmFjdGl2ZSxcbiAgICAmLmFjdGl2ZSB7XG4gICAgICBiYWNrZ3JvdW5kOiBkYXJrZW4oJHByaW1hcnktY29sb3ItZGFyaywgNCUpO1xuICAgICAgYm9yZGVyLWNvbG9yOiBkYXJrZW4oJHByaW1hcnktY29sb3ItZGFyaywgNyUpO1xuICAgICAgY29sb3I6ICRsaWdodC1jb2xvcjtcbiAgICB9XG4gICAgJi5sb2FkaW5nIHtcbiAgICAgICY6OmFmdGVyIHtcbiAgICAgICAgYm9yZGVyLWJvdHRvbS1jb2xvcjogJGxpZ2h0LWNvbG9yO1xuICAgICAgICBib3JkZXItbGVmdC1jb2xvcjogJGxpZ2h0LWNvbG9yO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8vIEJ1dHRvbiBDb2xvcnNcbiAgJi5idG4tc3VjY2VzcyB7XG4gICAgQGluY2x1ZGUgYnV0dG9uLXZhcmlhbnQoJHN1Y2Nlc3MtY29sb3IpO1xuICB9XG5cbiAgJi5idG4tZXJyb3Ige1xuICAgIEBpbmNsdWRlIGJ1dHRvbi12YXJpYW50KCRlcnJvci1jb2xvcik7XG4gIH1cblxuICAvLyBCdXR0b24gTGlua1xuICAmLmJ0bi1saW5rIHtcbiAgICBiYWNrZ3JvdW5kOiB0cmFuc3BhcmVudDtcbiAgICBib3JkZXItY29sb3I6IHRyYW5zcGFyZW50O1xuICAgIGNvbG9yOiAkbGluay1jb2xvcjtcbiAgICAmOmZvY3VzLFxuICAgICY6aG92ZXIsXG4gICAgJjphY3RpdmUsXG4gICAgJi5hY3RpdmUge1xuICAgICAgY29sb3I6ICRsaW5rLWNvbG9yLWRhcms7XG4gICAgfVxuICB9XG5cbiAgLy8gQnV0dG9uIFNpemVzXG4gICYuYnRuLXNtIHtcbiAgICBmb250LXNpemU6ICRmb250LXNpemUtc207XG4gICAgaGVpZ2h0OiAkY29udHJvbC1zaXplLXNtO1xuICAgIHBhZGRpbmc6ICRjb250cm9sLXBhZGRpbmcteS1zbSAkY29udHJvbC1wYWRkaW5nLXgtc207XG4gIH1cblxuICAmLmJ0bi1sZyB7XG4gICAgZm9udC1zaXplOiAkZm9udC1zaXplLWxnO1xuICAgIGhlaWdodDogJGNvbnRyb2wtc2l6ZS1sZztcbiAgICBwYWRkaW5nOiAkY29udHJvbC1wYWRkaW5nLXktbGcgJGNvbnRyb2wtcGFkZGluZy14LWxnO1xuICB9XG5cbiAgLy8gQnV0dG9uIEJsb2NrXG4gICYuYnRuLWJsb2NrIHtcbiAgICBkaXNwbGF5OiBibG9jaztcbiAgICB3aWR0aDogMTAwJTtcbiAgfVxuXG4gIC8vIEJ1dHRvbiBBY3Rpb25cbiAgJi5idG4tYWN0aW9uIHtcbiAgICB3aWR0aDogJGNvbnRyb2wtc2l6ZTtcbiAgICBwYWRkaW5nLWxlZnQ6IDA7XG4gICAgcGFkZGluZy1yaWdodDogMDtcblxuICAgICYuYnRuLXNtIHtcbiAgICAgIHdpZHRoOiAkY29udHJvbC1zaXplLXNtO1xuICAgIH1cblxuICAgICYuYnRuLWxnIHtcbiAgICAgIHdpZHRoOiAkY29udHJvbC1zaXplLWxnO1xuICAgIH1cbiAgfVxuXG4gIC8vIEJ1dHRvbiBDbGVhclxuICAmLmJ0bi1jbGVhciB7XG4gICAgYmFja2dyb3VuZDogdHJhbnNwYXJlbnQ7XG4gICAgYm9yZGVyOiAwO1xuICAgIGNvbG9yOiBjdXJyZW50Q29sb3I7XG4gICAgaGVpZ2h0OiAkdW5pdC01O1xuICAgIGxpbmUtaGVpZ2h0OiAkdW5pdC00O1xuICAgIG1hcmdpbi1sZWZ0OiAkdW5pdC0xO1xuICAgIG1hcmdpbi1yaWdodDogLTJweDtcbiAgICBvcGFjaXR5OiAxO1xuICAgIHBhZGRpbmc6ICR1bml0LWg7XG4gICAgdGV4dC1kZWNvcmF0aW9uOiBub25lO1xuICAgIHdpZHRoOiAkdW5pdC01O1xuXG4gICAgJjpmb2N1cyxcbiAgICAmOmhvdmVyIHtcbiAgICAgIGJhY2tncm91bmQ6IHJnYmEoJGJnLWNvbG9yLCAuNSk7XG4gICAgICBvcGFjaXR5OiAuOTU7XG4gICAgfVxuXG4gICAgJjo6YmVmb3JlIHtcbiAgICAgIGNvbnRlbnQ6IFwiXFwyNzE1XCI7XG4gICAgfVxuICB9XG59XG5cbi8vIEJ1dHRvbiBncm91cHNcbi5idG4tZ3JvdXAge1xuICBkaXNwbGF5OiBpbmxpbmUtZmxleDtcbiAgZmxleC13cmFwOiB3cmFwO1xuXG4gIC5idG4ge1xuICAgIGZsZXg6IDEgMCBhdXRvO1xuICAgICY6Zmlyc3QtY2hpbGQ6bm90KDpsYXN0LWNoaWxkKSB7XG4gICAgICBib3JkZXItYm90dG9tLXJpZ2h0LXJhZGl1czogMDtcbiAgICAgIGJvcmRlci10b3AtcmlnaHQtcmFkaXVzOiAwO1xuICAgIH1cbiAgICAmOm5vdCg6Zmlyc3QtY2hpbGQpOm5vdCg6bGFzdC1jaGlsZCkge1xuICAgICAgYm9yZGVyLXJhZGl1czogMDtcbiAgICAgIG1hcmdpbi1sZWZ0OiAtJGJvcmRlci13aWR0aDtcbiAgICB9XG4gICAgJjpsYXN0LWNoaWxkOm5vdCg6Zmlyc3QtY2hpbGQpIHtcbiAgICAgIGJvcmRlci1ib3R0b20tbGVmdC1yYWRpdXM6IDA7XG4gICAgICBib3JkZXItdG9wLWxlZnQtcmFkaXVzOiAwO1xuICAgICAgbWFyZ2luLWxlZnQ6IC0kYm9yZGVyLXdpZHRoO1xuICAgIH1cbiAgICAmOmZvY3VzLFxuICAgICY6aG92ZXIsXG4gICAgJjphY3RpdmUsXG4gICAgJi5hY3RpdmUge1xuICAgICAgei1pbmRleDogJHppbmRleC0wO1xuICAgIH1cbiAgfVxuXG4gICYuYnRuLWdyb3VwLWJsb2NrIHtcbiAgICBkaXNwbGF5OiBmbGV4O1xuXG4gICAgLmJ0biB7XG4gICAgICBmbGV4OiAxIDAgMDtcbiAgICB9XG4gIH1cbn1cbiIsIi8vIEZvcm1zXG4uZm9ybS1ncm91cCB7XG4gICY6bm90KDpsYXN0LWNoaWxkKSB7XG4gICAgbWFyZ2luLWJvdHRvbTogJGxheW91dC1zcGFjaW5nO1xuICB9XG59XG5cbmZpZWxkc2V0IHtcbiAgbWFyZ2luLWJvdHRvbTogJGxheW91dC1zcGFjaW5nLWxnO1xufVxuXG5sZWdlbmQge1xuICBmb250LXNpemU6ICRmb250LXNpemUtbGc7XG4gIGZvbnQtd2VpZ2h0OiA1MDA7XG4gIG1hcmdpbi1ib3R0b206ICRsYXlvdXQtc3BhY2luZy1sZztcbn1cblxuLy8gRm9ybSBlbGVtZW50OiBMYWJlbFxuLmZvcm0tbGFiZWwge1xuICBkaXNwbGF5OiBibG9jaztcbiAgbGluZS1oZWlnaHQ6ICRsaW5lLWhlaWdodDtcbiAgcGFkZGluZzogJGNvbnRyb2wtcGFkZGluZy15ICsgJGJvcmRlci13aWR0aCAwO1xuXG4gICYubGFiZWwtc20ge1xuICAgIGZvbnQtc2l6ZTogJGZvbnQtc2l6ZS1zbTtcbiAgICBwYWRkaW5nOiAkY29udHJvbC1wYWRkaW5nLXktc20gKyAkYm9yZGVyLXdpZHRoIDA7XG4gIH1cblxuICAmLmxhYmVsLWxnIHtcbiAgICBmb250LXNpemU6ICRmb250LXNpemUtbGc7XG4gICAgcGFkZGluZzogJGNvbnRyb2wtcGFkZGluZy15LWxnICsgJGJvcmRlci13aWR0aCAwO1xuICB9XG59XG5cbi8vIEZvcm0gZWxlbWVudDogSW5wdXRcbi5mb3JtLWlucHV0IHtcbiAgYXBwZWFyYW5jZTogbm9uZTtcbiAgYmFja2dyb3VuZDogJGJnLWNvbG9yLWxpZ2h0O1xuICBiYWNrZ3JvdW5kLWltYWdlOiBub25lO1xuICBib3JkZXI6ICRib3JkZXItd2lkdGggc29saWQgJGJvcmRlci1jb2xvci1kYXJrO1xuICBib3JkZXItcmFkaXVzOiAkYm9yZGVyLXJhZGl1cztcbiAgY29sb3I6ICRib2R5LWZvbnQtY29sb3I7XG4gIGRpc3BsYXk6IGJsb2NrO1xuICBmb250LXNpemU6ICRmb250LXNpemU7XG4gIGhlaWdodDogJGNvbnRyb2wtc2l6ZTtcbiAgbGluZS1oZWlnaHQ6ICRsaW5lLWhlaWdodDtcbiAgbWF4LXdpZHRoOiAxMDAlO1xuICBvdXRsaW5lOiBub25lO1xuICBwYWRkaW5nOiAkY29udHJvbC1wYWRkaW5nLXkgJGNvbnRyb2wtcGFkZGluZy14O1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gIHRyYW5zaXRpb246IGJhY2tncm91bmQgLjJzLCBib3JkZXIgLjJzLCBib3gtc2hhZG93IC4ycywgY29sb3IgLjJzO1xuICB3aWR0aDogMTAwJTtcbiAgJjpmb2N1cyB7XG4gICAgQGluY2x1ZGUgY29udHJvbC1zaGFkb3coKTtcbiAgICBib3JkZXItY29sb3I6ICRwcmltYXJ5LWNvbG9yO1xuICB9XG4gICY6OnBsYWNlaG9sZGVyIHtcbiAgICBjb2xvcjogJGdyYXktY29sb3I7XG4gIH1cblxuICAvLyBJbnB1dCBzaXplc1xuICAmLmlucHV0LXNtIHtcbiAgICBmb250LXNpemU6ICRmb250LXNpemUtc207XG4gICAgaGVpZ2h0OiAkY29udHJvbC1zaXplLXNtO1xuICAgIHBhZGRpbmc6ICRjb250cm9sLXBhZGRpbmcteS1zbSAkY29udHJvbC1wYWRkaW5nLXgtc207XG4gIH1cblxuICAmLmlucHV0LWxnIHtcbiAgICBmb250LXNpemU6ICRmb250LXNpemUtbGc7XG4gICAgaGVpZ2h0OiAkY29udHJvbC1zaXplLWxnO1xuICAgIHBhZGRpbmc6ICRjb250cm9sLXBhZGRpbmcteS1sZyAkY29udHJvbC1wYWRkaW5nLXgtbGc7XG4gIH1cblxuICAmLmlucHV0LWlubGluZSB7XG4gICAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xuICAgIHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7XG4gICAgd2lkdGg6IGF1dG87XG4gIH1cblxuICAvLyBJbnB1dCB0eXBlc1xuICAmW3R5cGU9XCJmaWxlXCJdIHtcbiAgICBoZWlnaHQ6IGF1dG87XG4gIH1cbn1cblxuLy8gRm9ybSBlbGVtZW50OiBUZXh0YXJlYVxudGV4dGFyZWEuZm9ybS1pbnB1dCB7XG4gICYsXG4gICYuaW5wdXQtbGcsXG4gICYuaW5wdXQtc20ge1xuICAgIGhlaWdodDogYXV0bztcbiAgfVxufVxuXG4vLyBGb3JtIGVsZW1lbnQ6IElucHV0IGhpbnRcbi5mb3JtLWlucHV0LWhpbnQge1xuICBjb2xvcjogJGdyYXktY29sb3I7XG4gIGZvbnQtc2l6ZTogJGZvbnQtc2l6ZS1zbTtcbiAgbWFyZ2luLXRvcDogJHVuaXQtMTtcblxuICAuaGFzLXN1Y2Nlc3MgJixcbiAgLmlzLXN1Y2Nlc3MgKyAmIHtcbiAgICBjb2xvcjogJHN1Y2Nlc3MtY29sb3I7XG4gIH1cblxuICAuaGFzLWVycm9yICYsXG4gIC5pcy1lcnJvciArICYge1xuICAgIGNvbG9yOiAkZXJyb3ItY29sb3I7XG4gIH1cbn1cblxuLy8gRm9ybSBlbGVtZW50OiBTZWxlY3Rcbi5mb3JtLXNlbGVjdCB7XG4gIGFwcGVhcmFuY2U6IG5vbmU7XG4gIGJvcmRlcjogJGJvcmRlci13aWR0aCBzb2xpZCAkYm9yZGVyLWNvbG9yLWRhcms7XG4gIGJvcmRlci1yYWRpdXM6ICRib3JkZXItcmFkaXVzO1xuICBjb2xvcjogaW5oZXJpdDtcbiAgZm9udC1zaXplOiAkZm9udC1zaXplO1xuICBoZWlnaHQ6ICRjb250cm9sLXNpemU7XG4gIGxpbmUtaGVpZ2h0OiAkbGluZS1oZWlnaHQ7XG4gIG91dGxpbmU6IG5vbmU7XG4gIHBhZGRpbmc6ICRjb250cm9sLXBhZGRpbmcteSAkY29udHJvbC1wYWRkaW5nLXg7XG4gIHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7XG4gIHdpZHRoOiAxMDAlO1xuICBiYWNrZ3JvdW5kOiAkYmctY29sb3ItbGlnaHQ7IFxuICAmOmZvY3VzIHtcbiAgICBAaW5jbHVkZSBjb250cm9sLXNoYWRvdygpO1xuICAgIGJvcmRlci1jb2xvcjogJHByaW1hcnktY29sb3I7XG4gIH1cbiAgJjo6LW1zLWV4cGFuZCB7XG4gICAgZGlzcGxheTogbm9uZTtcbiAgfVxuXG4gIC8vIFNlbGVjdCBzaXplc1xuICAmLnNlbGVjdC1zbSB7XG4gICAgZm9udC1zaXplOiAkZm9udC1zaXplLXNtO1xuICAgIGhlaWdodDogJGNvbnRyb2wtc2l6ZS1zbTtcbiAgICBwYWRkaW5nOiAkY29udHJvbC1wYWRkaW5nLXktc20gKCRjb250cm9sLWljb24tc2l6ZSArICRjb250cm9sLXBhZGRpbmcteC1zbSkgJGNvbnRyb2wtcGFkZGluZy15LXNtICRjb250cm9sLXBhZGRpbmcteC1zbTtcbiAgfVxuXG4gICYuc2VsZWN0LWxnIHtcbiAgICBmb250LXNpemU6ICRmb250LXNpemUtbGc7XG4gICAgaGVpZ2h0OiAkY29udHJvbC1zaXplLWxnO1xuICAgIHBhZGRpbmc6ICRjb250cm9sLXBhZGRpbmcteS1sZyAoJGNvbnRyb2wtaWNvbi1zaXplICsgJGNvbnRyb2wtcGFkZGluZy14LWxnKSAkY29udHJvbC1wYWRkaW5nLXktbGcgJGNvbnRyb2wtcGFkZGluZy14LWxnO1xuICB9XG5cbiAgLy8gTXVsdGlwbGUgc2VsZWN0XG4gICZbc2l6ZV0sXG4gICZbbXVsdGlwbGVdIHtcbiAgICBoZWlnaHQ6IGF1dG87XG4gICAgcGFkZGluZzogJGNvbnRyb2wtcGFkZGluZy15ICRjb250cm9sLXBhZGRpbmcteDtcblxuICAgIG9wdGlvbiB7XG4gICAgICBwYWRkaW5nOiAkdW5pdC1oICR1bml0LTE7XG4gICAgfVxuICB9XG4gICY6bm90KFttdWx0aXBsZV0pOm5vdChbc2l6ZV0pIHtcbiAgICBiYWNrZ3JvdW5kOiAkYmctY29sb3ItbGlnaHQgdXJsKFwiZGF0YTppbWFnZS9zdmcreG1sO2NoYXJzZXQ9dXRmOCwlM0NzdmclMjB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnJTIwdmlld0JveD0nMCUyMDAlMjA0JTIwNSclM0UlM0NwYXRoJTIwZmlsbD0nJTIzNjY3MTg5JyUyMGQ9J00yJTIwMEwwJTIwMmg0em0wJTIwNUwwJTIwM2g0eicvJTNFJTNDL3N2ZyUzRVwiKSBuby1yZXBlYXQgcmlnaHQgLjM1cmVtIGNlbnRlciAvIC40cmVtIC41cmVtO1xuICAgIHBhZGRpbmctcmlnaHQ6ICRjb250cm9sLWljb24tc2l6ZSArICRjb250cm9sLXBhZGRpbmcteDtcbiAgfVxufVxuXG4vLyBGb3JtIEljb25zXG4uaGFzLWljb24tbGVmdCxcbi5oYXMtaWNvbi1yaWdodCB7XG4gIHBvc2l0aW9uOiByZWxhdGl2ZTtcblxuICAuZm9ybS1pY29uIHtcbiAgICBoZWlnaHQ6ICRjb250cm9sLWljb24tc2l6ZTtcbiAgICBtYXJnaW46IDAgJGNvbnRyb2wtcGFkZGluZy15O1xuICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICB0b3A6IDUwJTtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVkoLTUwJSk7XG4gICAgd2lkdGg6ICRjb250cm9sLWljb24tc2l6ZTtcbiAgICB6LWluZGV4OiAkemluZGV4LTAgKyAxO1xuICB9XG59XG5cbi5oYXMtaWNvbi1sZWZ0IHtcbiAgLmZvcm0taWNvbiB7XG4gICAgbGVmdDogJGJvcmRlci13aWR0aDtcbiAgfVxuXG4gIC5mb3JtLWlucHV0IHtcbiAgICBwYWRkaW5nLWxlZnQ6ICRjb250cm9sLWljb24tc2l6ZSArICRjb250cm9sLXBhZGRpbmcteSAqIDI7XG4gIH1cbn1cblxuLmhhcy1pY29uLXJpZ2h0IHtcbiAgLmZvcm0taWNvbiB7XG4gICAgcmlnaHQ6ICRib3JkZXItd2lkdGg7XG4gIH1cblxuICAuZm9ybS1pbnB1dCB7XG4gICAgcGFkZGluZy1yaWdodDogJGNvbnRyb2wtaWNvbi1zaXplICsgJGNvbnRyb2wtcGFkZGluZy15ICogMjtcbiAgfVxufVxuXG4vLyBGb3JtIGVsZW1lbnQ6IENoZWNrYm94IGFuZCBSYWRpb1xuLmZvcm0tY2hlY2tib3gsXG4uZm9ybS1yYWRpbyxcbi5mb3JtLXN3aXRjaCB7XG4gIGRpc3BsYXk6IGJsb2NrO1xuICBsaW5lLWhlaWdodDogJGxpbmUtaGVpZ2h0O1xuICBtYXJnaW46ICgkY29udHJvbC1zaXplIC0gJGNvbnRyb2wtc2l6ZS1zbSkgLyAyIDA7XG4gIG1pbi1oZWlnaHQ6ICRjb250cm9sLXNpemUtc207XG4gIHBhZGRpbmc6ICgoJGNvbnRyb2wtc2l6ZS1zbSAtICRsaW5lLWhlaWdodCkgLyAyKSAkY29udHJvbC1wYWRkaW5nLXggKCgkY29udHJvbC1zaXplLXNtIC0gJGxpbmUtaGVpZ2h0KSAvIDIpICgkY29udHJvbC1pY29uLXNpemUgKyAkY29udHJvbC1wYWRkaW5nLXgpO1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG5cbiAgaW5wdXQge1xuICAgIGNsaXA6IHJlY3QoMCwgMCwgMCwgMCk7XG4gICAgaGVpZ2h0OiAxcHg7XG4gICAgbWFyZ2luOiAtMXB4O1xuICAgIG92ZXJmbG93OiBoaWRkZW47XG4gICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgIHdpZHRoOiAxcHg7XG4gICAgJjpmb2N1cyArIC5mb3JtLWljb24ge1xuICAgICAgQGluY2x1ZGUgY29udHJvbC1zaGFkb3coKTtcbiAgICAgIGJvcmRlci1jb2xvcjogJHByaW1hcnktY29sb3I7XG4gICAgfVxuICAgICY6Y2hlY2tlZCArIC5mb3JtLWljb24ge1xuICAgICAgYmFja2dyb3VuZDogJHByaW1hcnktY29sb3I7XG4gICAgICBib3JkZXItY29sb3I6ICRwcmltYXJ5LWNvbG9yO1xuICAgIH1cbiAgfVxuXG4gIC5mb3JtLWljb24ge1xuICAgIGJvcmRlcjogJGJvcmRlci13aWR0aCBzb2xpZCAkYm9yZGVyLWNvbG9yLWRhcms7XG4gICAgY3Vyc29yOiBwb2ludGVyO1xuICAgIGRpc3BsYXk6IGlubGluZS1ibG9jaztcbiAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgdHJhbnNpdGlvbjogYmFja2dyb3VuZCAuMnMsIGJvcmRlciAuMnMsIGJveC1zaGFkb3cgLjJzLCBjb2xvciAuMnM7XG4gIH1cblxuICAvLyBJbnB1dCBjaGVja2JveCwgcmFkaW8gYW5kIHN3aXRjaCBzaXplc1xuICAmLmlucHV0LXNtIHtcbiAgICBmb250LXNpemU6ICRmb250LXNpemUtc207XG4gICAgbWFyZ2luOiAwO1xuICB9XG5cbiAgJi5pbnB1dC1sZyB7XG4gICAgZm9udC1zaXplOiAkZm9udC1zaXplLWxnO1xuICAgIG1hcmdpbjogKCRjb250cm9sLXNpemUtbGcgLSAkY29udHJvbC1zaXplLXNtKSAvIDIgMDtcbiAgfVxufVxuXG4uZm9ybS1jaGVja2JveCxcbi5mb3JtLXJhZGlvIHtcbiAgLmZvcm0taWNvbiB7XG4gICAgYmFja2dyb3VuZDogJGJnLWNvbG9yLWxpZ2h0O1xuICAgIGhlaWdodDogJGNvbnRyb2wtaWNvbi1zaXplO1xuICAgIGxlZnQ6IDA7XG4gICAgdG9wOiAoJGNvbnRyb2wtc2l6ZS1zbSAtICRjb250cm9sLWljb24tc2l6ZSkgLyAyO1xuICAgIHdpZHRoOiAkY29udHJvbC1pY29uLXNpemU7XG4gIH1cblxuICBpbnB1dCB7XG4gICAgJjphY3RpdmUgKyAuZm9ybS1pY29uIHtcbiAgICAgIGJhY2tncm91bmQ6ICRiZy1jb2xvci1kYXJrO1xuICAgIH1cbiAgfVxufVxuLmZvcm0tY2hlY2tib3gge1xuICAuZm9ybS1pY29uIHtcbiAgICBib3JkZXItcmFkaXVzOiAkYm9yZGVyLXJhZGl1cztcbiAgfVxuXG4gIGlucHV0IHtcbiAgICAmOmNoZWNrZWQgKyAuZm9ybS1pY29uIHtcbiAgICAgICY6OmJlZm9yZSB7XG4gICAgICAgIGJhY2tncm91bmQtY2xpcDogcGFkZGluZy1ib3g7XG4gICAgICAgIGJvcmRlcjogJGJvcmRlci13aWR0aC1sZyBzb2xpZCAkbGlnaHQtY29sb3I7XG4gICAgICAgIGJvcmRlci1sZWZ0LXdpZHRoOiAwO1xuICAgICAgICBib3JkZXItdG9wLXdpZHRoOiAwO1xuICAgICAgICBjb250ZW50OiBcIlwiO1xuICAgICAgICBoZWlnaHQ6IDlweDtcbiAgICAgICAgbGVmdDogNTAlO1xuICAgICAgICBtYXJnaW4tbGVmdDogLTNweDtcbiAgICAgICAgbWFyZ2luLXRvcDogLTZweDtcbiAgICAgICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgICAgICB0b3A6IDUwJTtcbiAgICAgICAgdHJhbnNmb3JtOiByb3RhdGUoNDVkZWcpO1xuICAgICAgICB3aWR0aDogNnB4O1xuICAgICAgfVxuICAgIH1cbiAgICAmOmluZGV0ZXJtaW5hdGUgKyAuZm9ybS1pY29uIHtcbiAgICAgIGJhY2tncm91bmQ6ICRwcmltYXJ5LWNvbG9yO1xuICAgICAgYm9yZGVyLWNvbG9yOiAkcHJpbWFyeS1jb2xvcjtcbiAgICAgICY6OmJlZm9yZSB7XG4gICAgICAgIGJhY2tncm91bmQ6ICRiZy1jb2xvci1saWdodDtcbiAgICAgICAgY29udGVudDogXCJcIjtcbiAgICAgICAgaGVpZ2h0OiAycHg7XG4gICAgICAgIGxlZnQ6IDUwJTtcbiAgICAgICAgbWFyZ2luLWxlZnQ6IC01cHg7XG4gICAgICAgIG1hcmdpbi10b3A6IC0xcHg7XG4gICAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICAgICAgdG9wOiA1MCU7XG4gICAgICAgIHdpZHRoOiAxMHB4O1xuICAgICAgfVxuICAgIH1cbiAgfVxufVxuLmZvcm0tcmFkaW8ge1xuICAuZm9ybS1pY29uIHtcbiAgICBib3JkZXItcmFkaXVzOiA1MCU7XG4gIH1cblxuICBpbnB1dCB7XG4gICAgJjpjaGVja2VkICsgLmZvcm0taWNvbiB7XG4gICAgICAmOjpiZWZvcmUge1xuICAgICAgICBiYWNrZ3JvdW5kOiAkYmctY29sb3ItbGlnaHQ7XG4gICAgICAgIGJvcmRlci1yYWRpdXM6IDUwJTtcbiAgICAgICAgY29udGVudDogXCJcIjtcbiAgICAgICAgaGVpZ2h0OiA2cHg7XG4gICAgICAgIGxlZnQ6IDUwJTtcbiAgICAgICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgICAgICB0b3A6IDUwJTtcbiAgICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUoLTUwJSwgLTUwJSk7XG4gICAgICAgIHdpZHRoOiA2cHg7XG4gICAgICB9XG4gICAgfVxuICB9XG59XG5cbi8vIEZvcm0gZWxlbWVudDogU3dpdGNoXG4uZm9ybS1zd2l0Y2gge1xuICBwYWRkaW5nLWxlZnQ6ICgkdW5pdC04ICsgJGNvbnRyb2wtcGFkZGluZy14KTtcblxuICAuZm9ybS1pY29uIHtcbiAgICBiYWNrZ3JvdW5kOiAkZ3JheS1jb2xvcjtcbiAgICBiYWNrZ3JvdW5kLWNsaXA6IHBhZGRpbmctYm94O1xuICAgIGJvcmRlci1yYWRpdXM6ICR1bml0LTIgKyAkYm9yZGVyLXdpZHRoO1xuICAgIGhlaWdodDogJHVuaXQtNCArICRib3JkZXItd2lkdGggKiAyO1xuICAgIGxlZnQ6IDA7XG4gICAgdG9wOiAoJGNvbnRyb2wtc2l6ZS1zbSAtICR1bml0LTQpIC8gMiAtICRib3JkZXItd2lkdGg7XG4gICAgd2lkdGg6ICR1bml0LTg7XG4gICAgJjo6YmVmb3JlIHtcbiAgICAgIGJhY2tncm91bmQ6ICRiZy1jb2xvci1saWdodDtcbiAgICAgIGJvcmRlci1yYWRpdXM6IDUwJTtcbiAgICAgIGNvbnRlbnQ6IFwiXCI7XG4gICAgICBkaXNwbGF5OiBibG9jaztcbiAgICAgIGhlaWdodDogJHVuaXQtNDtcbiAgICAgIGxlZnQ6IDA7XG4gICAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgICB0b3A6IDA7XG4gICAgICB0cmFuc2l0aW9uOiBiYWNrZ3JvdW5kIC4ycywgYm9yZGVyIC4ycywgYm94LXNoYWRvdyAuMnMsIGNvbG9yIC4ycywgbGVmdCAuMnM7XG4gICAgICB3aWR0aDogJHVuaXQtNDtcbiAgICB9XG4gIH1cblxuICBpbnB1dCB7XG4gICAgJjpjaGVja2VkICsgLmZvcm0taWNvbiB7XG4gICAgICAmOjpiZWZvcmUge1xuICAgICAgICBsZWZ0OiAxNHB4O1xuICAgICAgfVxuICAgIH1cbiAgICAmOmFjdGl2ZSArIC5mb3JtLWljb24ge1xuICAgICAgJjo6YmVmb3JlIHtcbiAgICAgICAgYmFja2dyb3VuZDogJGJnLWNvbG9yO1xuICAgICAgfVxuICAgIH1cbiAgfVxufVxuXG4vLyBGb3JtIGVsZW1lbnQ6IElucHV0IGdyb3Vwc1xuLmlucHV0LWdyb3VwIHtcbiAgZGlzcGxheTogZmxleDtcblxuICAuaW5wdXQtZ3JvdXAtYWRkb24ge1xuICAgIGJhY2tncm91bmQ6ICRiZy1jb2xvcjtcbiAgICBib3JkZXI6ICRib3JkZXItd2lkdGggc29saWQgJGJvcmRlci1jb2xvci1kYXJrO1xuICAgIGJvcmRlci1yYWRpdXM6ICRib3JkZXItcmFkaXVzO1xuICAgIGxpbmUtaGVpZ2h0OiAkbGluZS1oZWlnaHQ7XG4gICAgcGFkZGluZzogJGNvbnRyb2wtcGFkZGluZy15ICRjb250cm9sLXBhZGRpbmcteDtcbiAgICB3aGl0ZS1zcGFjZTogbm93cmFwO1xuXG4gICAgJi5hZGRvbi1zbSB7XG4gICAgICBmb250LXNpemU6ICRmb250LXNpemUtc207XG4gICAgICBwYWRkaW5nOiAkY29udHJvbC1wYWRkaW5nLXktc20gJGNvbnRyb2wtcGFkZGluZy14LXNtO1xuICAgIH1cblxuICAgICYuYWRkb24tbGcge1xuICAgICAgZm9udC1zaXplOiAkZm9udC1zaXplLWxnO1xuICAgICAgcGFkZGluZzogJGNvbnRyb2wtcGFkZGluZy15LWxnICRjb250cm9sLXBhZGRpbmcteC1sZztcbiAgICB9XG4gIH1cblxuICAuZm9ybS1pbnB1dCxcbiAgLmZvcm0tc2VsZWN0IHtcbiAgICBmbGV4OiAxIDEgYXV0bztcbiAgICB3aWR0aDogMSU7XG4gIH1cblxuICAuaW5wdXQtZ3JvdXAtYnRuIHtcbiAgICB6LWluZGV4OiAkemluZGV4LTA7XG4gIH1cblxuICAuZm9ybS1pbnB1dCxcbiAgLmZvcm0tc2VsZWN0LFxuICAuaW5wdXQtZ3JvdXAtYWRkb24sXG4gIC5pbnB1dC1ncm91cC1idG4ge1xuICAgICY6Zmlyc3QtY2hpbGQ6bm90KDpsYXN0LWNoaWxkKSB7XG4gICAgICBib3JkZXItYm90dG9tLXJpZ2h0LXJhZGl1czogMDtcbiAgICAgIGJvcmRlci10b3AtcmlnaHQtcmFkaXVzOiAwO1xuICAgIH1cbiAgICAmOm5vdCg6Zmlyc3QtY2hpbGQpOm5vdCg6bGFzdC1jaGlsZCkge1xuICAgICAgYm9yZGVyLXJhZGl1czogMDtcbiAgICAgIG1hcmdpbi1sZWZ0OiAtJGJvcmRlci13aWR0aDtcbiAgICB9XG4gICAgJjpsYXN0LWNoaWxkOm5vdCg6Zmlyc3QtY2hpbGQpIHtcbiAgICAgIGJvcmRlci1ib3R0b20tbGVmdC1yYWRpdXM6IDA7XG4gICAgICBib3JkZXItdG9wLWxlZnQtcmFkaXVzOiAwO1xuICAgICAgbWFyZ2luLWxlZnQ6IC0kYm9yZGVyLXdpZHRoO1xuICAgIH1cbiAgICAmOmZvY3VzIHtcbiAgICAgIHotaW5kZXg6ICR6aW5kZXgtMCArIDE7XG4gICAgfVxuICB9XG5cbiAgLmZvcm0tc2VsZWN0IHtcbiAgICB3aWR0aDogYXV0bztcbiAgfVxuXG4gICYuaW5wdXQtaW5saW5lIHtcbiAgICBkaXNwbGF5OiBpbmxpbmUtZmxleDtcbiAgfVxufVxuXG4vLyBGb3JtIHZhbGlkYXRpb24gc3RhdGVzXG4uZm9ybS1pbnB1dCxcbi5mb3JtLXNlbGVjdCB7XG4gIC5oYXMtc3VjY2VzcyAmLFxuICAmLmlzLXN1Y2Nlc3Mge1xuICAgIGJhY2tncm91bmQ6IGxpZ2h0ZW4oJHN1Y2Nlc3MtY29sb3IsIDUzJSk7XG4gICAgYm9yZGVyLWNvbG9yOiAkc3VjY2Vzcy1jb2xvcjtcbiAgICAmOmZvY3VzIHtcbiAgICAgIEBpbmNsdWRlIGNvbnRyb2wtc2hhZG93KCRzdWNjZXNzLWNvbG9yKTtcbiAgICB9XG4gIH1cblxuICAuaGFzLWVycm9yICYsXG4gICYuaXMtZXJyb3Ige1xuICAgIGJhY2tncm91bmQ6IGxpZ2h0ZW4oJGVycm9yLWNvbG9yLCA1MyUpO1xuICAgIGJvcmRlci1jb2xvcjogJGVycm9yLWNvbG9yO1xuICAgICY6Zm9jdXMge1xuICAgICAgQGluY2x1ZGUgY29udHJvbC1zaGFkb3coJGVycm9yLWNvbG9yKTtcbiAgICB9XG4gIH1cbn1cblxuLmZvcm0tY2hlY2tib3gsXG4uZm9ybS1yYWRpbyxcbi5mb3JtLXN3aXRjaCB7XG4gIC5oYXMtZXJyb3IgJixcbiAgJi5pcy1lcnJvciB7XG4gICAgLmZvcm0taWNvbiB7XG4gICAgICBib3JkZXItY29sb3I6ICRlcnJvci1jb2xvcjtcbiAgICB9XG5cbiAgICBpbnB1dCB7XG4gICAgICAmOmNoZWNrZWQgKyAuZm9ybS1pY29uIHtcbiAgICAgICAgYmFja2dyb3VuZDogJGVycm9yLWNvbG9yO1xuICAgICAgICBib3JkZXItY29sb3I6ICRlcnJvci1jb2xvcjtcbiAgICAgIH1cblxuICAgICAgJjpmb2N1cyArIC5mb3JtLWljb24ge1xuICAgICAgICBAaW5jbHVkZSBjb250cm9sLXNoYWRvdygkZXJyb3ItY29sb3IpO1xuICAgICAgICBib3JkZXItY29sb3I6ICRlcnJvci1jb2xvcjtcbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cblxuLmZvcm0tY2hlY2tib3gge1xuICAuaGFzLWVycm9yICYsXG4gICYuaXMtZXJyb3Ige1xuICAgIGlucHV0IHtcbiAgICAgICY6aW5kZXRlcm1pbmF0ZSArIC5mb3JtLWljb24ge1xuICAgICAgICBiYWNrZ3JvdW5kOiAkZXJyb3ItY29sb3I7XG4gICAgICAgIGJvcmRlci1jb2xvcjogJGVycm9yLWNvbG9yO1xuICAgICAgfVxuICAgIH1cbiAgfVxufVxuXG4vLyB2YWxpZGF0aW9uIGJhc2VkIG9uIDpwbGFjZWhvbGRlci1zaG93biAoRWRnZSBkb2Vzbid0IHN1cHBvcnQgaXQgeWV0KVxuLmZvcm0taW5wdXQge1xuICAmOm5vdCg6cGxhY2Vob2xkZXItc2hvd24pIHtcbiAgICAmOmludmFsaWQge1xuICAgICAgYm9yZGVyLWNvbG9yOiAkZXJyb3ItY29sb3I7XG4gICAgICAmOmZvY3VzIHtcbiAgICAgICAgQGluY2x1ZGUgY29udHJvbC1zaGFkb3coJGVycm9yLWNvbG9yKTtcbiAgICAgICAgYmFja2dyb3VuZDogbGlnaHRlbigkZXJyb3ItY29sb3IsIDUzJSk7XG4gICAgICB9XG5cbiAgICAgICYgKyAuZm9ybS1pbnB1dC1oaW50IHtcbiAgICAgICAgY29sb3I6ICRlcnJvci1jb2xvcjtcbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cblxuLy8gRm9ybSBkaXNhYmxlZCBhbmQgcmVhZG9ubHlcbi5mb3JtLWlucHV0LFxuLmZvcm0tc2VsZWN0IHtcbiAgJjpkaXNhYmxlZCxcbiAgJi5kaXNhYmxlZCB7XG4gICAgYmFja2dyb3VuZC1jb2xvcjogJGJnLWNvbG9yLWRhcms7XG4gICAgY3Vyc29yOiBub3QtYWxsb3dlZDtcbiAgICBvcGFjaXR5OiAuNTtcbiAgfVxufVxuXG4uZm9ybS1pbnB1dCB7XG4gICZbcmVhZG9ubHldIHtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAkYmctY29sb3I7XG4gIH1cbn1cblxuaW5wdXQge1xuICAmOmRpc2FibGVkLFxuICAmLmRpc2FibGVkIHtcbiAgICAmICsgLmZvcm0taWNvbiB7XG4gICAgICBiYWNrZ3JvdW5kOiAkYmctY29sb3ItZGFyaztcbiAgICAgIGN1cnNvcjogbm90LWFsbG93ZWQ7XG4gICAgICBvcGFjaXR5OiAuNTtcbiAgICB9XG4gIH1cbn1cblxuLmZvcm0tc3dpdGNoIHtcbiAgaW5wdXQge1xuICAgICY6ZGlzYWJsZWQsXG4gICAgJi5kaXNhYmxlZCB7XG4gICAgICAmICsgLmZvcm0taWNvbjo6YmVmb3JlIHtcbiAgICAgICAgYmFja2dyb3VuZDogJGJnLWNvbG9yLWxpZ2h0O1xuICAgICAgfVxuICAgIH1cbiAgfVxufVxuXG4vLyBGb3JtIGhvcml6b250YWxcbi5mb3JtLWhvcml6b250YWwge1xuICBwYWRkaW5nOiAkbGF5b3V0LXNwYWNpbmcgMDtcblxuICAuZm9ybS1ncm91cCB7XG4gICAgZGlzcGxheTogZmxleDtcbiAgICBmbGV4LXdyYXA6IHdyYXA7XG4gIH1cbn1cblxuLy8gRm9ybSBpbmxpbmVcbi5mb3JtLWlubGluZSB7XG4gIGRpc3BsYXk6IGlubGluZS1ibG9jaztcbn1cbiIsIi8vIExhYmVsc1xuLmxhYmVsIHtcbiAgQGluY2x1ZGUgbGFiZWwtYmFzZSgpO1xuICBAaW5jbHVkZSBsYWJlbC12YXJpYW50KGxpZ2h0ZW4oJGJvZHktZm9udC1jb2xvciwgNSUpLCAkYmctY29sb3ItZGFyayk7XG4gIGRpc3BsYXk6IGlubGluZS1ibG9jaztcblxuICAvLyBMYWJlbCByb3VuZGVkXG4gICYubGFiZWwtcm91bmRlZCB7XG4gICAgYm9yZGVyLXJhZGl1czogNXJlbTtcbiAgICBwYWRkaW5nLWxlZnQ6IC40cmVtO1xuICAgIHBhZGRpbmctcmlnaHQ6IC40cmVtOyBcbiAgfVxuXG4gIC8vIExhYmVsIGNvbG9yc1xuICAmLmxhYmVsLXByaW1hcnkge1xuICAgIEBpbmNsdWRlIGxhYmVsLXZhcmlhbnQoJGxpZ2h0LWNvbG9yLCAkcHJpbWFyeS1jb2xvcik7XG4gIH1cblxuICAmLmxhYmVsLXNlY29uZGFyeSB7XG4gICAgQGluY2x1ZGUgbGFiZWwtdmFyaWFudCgkcHJpbWFyeS1jb2xvciwgJHNlY29uZGFyeS1jb2xvcik7XG4gIH1cblxuICAmLmxhYmVsLXN1Y2Nlc3Mge1xuICAgIEBpbmNsdWRlIGxhYmVsLXZhcmlhbnQoJGxpZ2h0LWNvbG9yLCAkc3VjY2Vzcy1jb2xvcik7XG4gIH1cblxuICAmLmxhYmVsLXdhcm5pbmcge1xuICAgIEBpbmNsdWRlIGxhYmVsLXZhcmlhbnQoJGxpZ2h0LWNvbG9yLCAkd2FybmluZy1jb2xvcik7XG4gIH1cblxuICAmLmxhYmVsLWVycm9yIHtcbiAgICBAaW5jbHVkZSBsYWJlbC12YXJpYW50KCRsaWdodC1jb2xvciwgJGVycm9yLWNvbG9yKTtcbiAgfVxufVxuIiwiLy8gQ29kZXNcbmNvZGUge1xuICBAaW5jbHVkZSBsYWJlbC1iYXNlKCk7XG4gIEBpbmNsdWRlIGxhYmVsLXZhcmlhbnQoJGNvZGUtY29sb3IsIGxpZ2h0ZW4oJGNvZGUtY29sb3IsIDQyLjUlKSk7XG4gIGZvbnQtc2l6ZTogODUlO1xufVxuXG4uY29kZSB7XG4gIGJvcmRlci1yYWRpdXM6ICRib3JkZXItcmFkaXVzO1xuICBjb2xvcjogJGJvZHktZm9udC1jb2xvcjtcbiAgcG9zaXRpb246IHJlbGF0aXZlO1xuXG4gICY6OmJlZm9yZSB7XG4gICAgY29sb3I6ICRncmF5LWNvbG9yO1xuICAgIGNvbnRlbnQ6IGF0dHIoZGF0YS1sYW5nKTtcbiAgICBmb250LXNpemU6ICRmb250LXNpemUtc207XG4gICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgIHJpZ2h0OiAkbGF5b3V0LXNwYWNpbmc7XG4gICAgdG9wOiAkdW5pdC1oO1xuICB9XG5cbiAgY29kZSB7XG4gICAgYmFja2dyb3VuZDogJGJnLWNvbG9yO1xuICAgIGNvbG9yOiBpbmhlcml0O1xuICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgIGxpbmUtaGVpZ2h0OiAxLjU7XG4gICAgb3ZlcmZsb3cteDogYXV0bztcbiAgICBwYWRkaW5nOiAxcmVtO1xuICAgIHdpZHRoOiAxMDAlO1xuICB9XG59XG4iLCIvLyBNZWRpYVxuLy8gSW1hZ2UgcmVzcG9uc2l2ZVxuLmltZy1yZXNwb25zaXZlIHtcbiAgZGlzcGxheTogYmxvY2s7XG4gIGhlaWdodDogYXV0bztcbiAgbWF4LXdpZHRoOiAxMDAlO1xufVxuXG4vLyBvYmplY3QtZml0IHN1cHBvcnQgaXMgY29taW5nIHRvIE1pY3Jvc29mdCBFZGdlXG4vLyBodHRwczovL2RldmVsb3Blci5taWNyb3NvZnQuY29tL2VuLXVzL21pY3Jvc29mdC1lZGdlL3BsYXRmb3JtL3N0YXR1cy9vYmplY3RmaXRhbmRvYmplY3Rwb3NpdGlvbi9cbi5pbWctZml0LWNvdmVyIHtcbiAgb2JqZWN0LWZpdDogY292ZXI7XG59XG5cbi5pbWctZml0LWNvbnRhaW4ge1xuICBvYmplY3QtZml0OiBjb250YWluO1xufVxuXG4vLyBWaWRlbyByZXNwb25zaXZlXG4udmlkZW8tcmVzcG9uc2l2ZSB7XG4gIGRpc3BsYXk6IGJsb2NrO1xuICBvdmVyZmxvdzogaGlkZGVuO1xuICBwYWRkaW5nOiAwO1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gIHdpZHRoOiAxMDAlO1xuICAmOjpiZWZvcmUge1xuICAgIGNvbnRlbnQ6IFwiXCI7XG4gICAgZGlzcGxheTogYmxvY2s7XG4gICAgcGFkZGluZy1ib3R0b206IDU2LjI1JTsgLy8gRGVmYXVsdCByYXRpbyAxNjo5LCB5b3UgY2FuIGNhbGN1bGF0ZSB0aGlzIHZhbHVlIGJ5IGRpdmlkaW5nIDkgYnkgMTZcbiAgfVxuXG4gIGlmcmFtZSxcbiAgb2JqZWN0LFxuICBlbWJlZCB7XG4gICAgYm9yZGVyOiAwO1xuICAgIGJvdHRvbTogMDtcbiAgICBoZWlnaHQ6IDEwMCU7XG4gICAgbGVmdDogMDtcbiAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgcmlnaHQ6IDA7XG4gICAgdG9wOiAwO1xuICAgIHdpZHRoOiAxMDAlO1xuICB9XG59XG5cbnZpZGVvLnZpZGVvLXJlc3BvbnNpdmUge1xuICBoZWlnaHQ6IGF1dG87XG4gIG1heC13aWR0aDogMTAwJTtcblxuICAmOjpiZWZvcmUge1xuICAgIGNvbnRlbnQ6IG5vbmU7XG4gIH1cbn1cblxuLnZpZGVvLXJlc3BvbnNpdmUtNC0zIHtcbiAgJjo6YmVmb3JlIHtcbiAgICBwYWRkaW5nLWJvdHRvbTogNzUlOyAvLyBSYXRpbyA0OjNcbiAgfVxufVxuXG4udmlkZW8tcmVzcG9uc2l2ZS0xLTEge1xuICAmOjpiZWZvcmUge1xuICAgIHBhZGRpbmctYm90dG9tOiAxMDAlOyAvLyBSYXRpbyAxOjFcbiAgfVxufVxuXG4vLyBGaWd1cmVcbi5maWd1cmUge1xuICBtYXJnaW46IDAgMCAkbGF5b3V0LXNwYWNpbmcgMDtcblxuICAuZmlndXJlLWNhcHRpb24ge1xuICAgIGNvbG9yOiAkZ3JheS1jb2xvci1kYXJrO1xuICAgIG1hcmdpbi10b3A6ICRsYXlvdXQtc3BhY2luZztcbiAgfVxufVxuIiwiLy8gTGF5b3V0XG4uY29udGFpbmVyIHtcbiAgbWFyZ2luLWxlZnQ6IGF1dG87XG4gIG1hcmdpbi1yaWdodDogYXV0bztcbiAgcGFkZGluZy1sZWZ0OiAkbGF5b3V0LXNwYWNpbmc7XG4gIHBhZGRpbmctcmlnaHQ6ICRsYXlvdXQtc3BhY2luZztcbiAgd2lkdGg6IDEwMCU7XG5cbiAgJGdyaWQtc3BhY2luZzogKCRsYXlvdXQtc3BhY2luZyAvICgkbGF5b3V0LXNwYWNpbmcgKiAwICsgMSkpICogJGh0bWwtZm9udC1zaXplO1xuXG4gICYuZ3JpZC14bCB7XG4gICAgbWF4LXdpZHRoOiAkZ3JpZC1zcGFjaW5nICogMiArICRzaXplLXhsO1xuICB9XG5cbiAgJi5ncmlkLWxnIHtcbiAgICBtYXgtd2lkdGg6ICRncmlkLXNwYWNpbmcgKiAyICsgJHNpemUtbGc7XG4gIH1cblxuICAmLmdyaWQtbWQge1xuICAgIG1heC13aWR0aDogJGdyaWQtc3BhY2luZyAqIDIgKyAkc2l6ZS1tZDtcbiAgfVxuXG4gICYuZ3JpZC1zbSB7XG4gICAgbWF4LXdpZHRoOiAkZ3JpZC1zcGFjaW5nICogMiArICRzaXplLXNtO1xuICB9XG5cbiAgJi5ncmlkLXhzIHtcbiAgICBtYXgtd2lkdGg6ICRncmlkLXNwYWNpbmcgKiAyICsgJHNpemUteHM7XG4gIH1cbn1cblxuLy8gUmVzcG9uc2l2ZSBicmVha3BvaW50IHN5c3RlbVxuLnNob3cteHMsXG4uc2hvdy1zbSxcbi5zaG93LW1kLFxuLnNob3ctbGcsXG4uc2hvdy14bCB7XG4gIGRpc3BsYXk6IG5vbmUgIWltcG9ydGFudDtcbn1cblxuLy8gUmVzcG9uc2l2ZSBncmlkIHN5c3RlbVxuLmNvbHVtbnMge1xuICBkaXNwbGF5OiBmbGV4O1xuICBmbGV4LXdyYXA6IHdyYXA7XG4gIG1hcmdpbi1sZWZ0OiAtJGxheW91dC1zcGFjaW5nO1xuICBtYXJnaW4tcmlnaHQ6IC0kbGF5b3V0LXNwYWNpbmc7XG5cbiAgJi5jb2wtZ2FwbGVzcyB7XG4gICAgbWFyZ2luLWxlZnQ6IDA7XG4gICAgbWFyZ2luLXJpZ2h0OiAwO1xuXG4gICAgJiA+IC5jb2x1bW4ge1xuICAgICAgcGFkZGluZy1sZWZ0OiAwO1xuICAgICAgcGFkZGluZy1yaWdodDogMDtcbiAgICB9XG4gIH1cbiAgJi5jb2wtb25lbGluZSB7XG4gICAgZmxleC13cmFwOiBub3dyYXA7XG4gICAgb3ZlcmZsb3cteDogYXV0bztcbiAgfVxufVxuLmNvbHVtbiB7XG4gIGZsZXg6IDE7XG4gIG1heC13aWR0aDogMTAwJTtcbiAgcGFkZGluZy1sZWZ0OiAkbGF5b3V0LXNwYWNpbmc7XG4gIHBhZGRpbmctcmlnaHQ6ICRsYXlvdXQtc3BhY2luZztcblxuICAmLmNvbC0xMixcbiAgJi5jb2wtMTEsXG4gICYuY29sLTEwLFxuICAmLmNvbC05LFxuICAmLmNvbC04LFxuICAmLmNvbC03LFxuICAmLmNvbC02LFxuICAmLmNvbC01LFxuICAmLmNvbC00LFxuICAmLmNvbC0zLFxuICAmLmNvbC0yLFxuICAmLmNvbC0xLFxuICAmLmNvbC1hdXRvIHtcbiAgICBmbGV4OiBub25lO1xuICB9XG59XG4uY29sLTEyIHtcbiAgd2lkdGg6IDEwMCU7XG59XG4uY29sLTExIHtcbiAgd2lkdGg6IDkxLjY2NjY2NjY3JTtcbn1cbi5jb2wtMTAge1xuICB3aWR0aDogODMuMzMzMzMzMzMlO1xufVxuLmNvbC05IHtcbiAgd2lkdGg6IDc1JTtcbn1cbi5jb2wtOCB7XG4gIHdpZHRoOiA2Ni42NjY2NjY2NyU7XG59XG4uY29sLTcge1xuICB3aWR0aDogNTguMzMzMzMzMzMlO1xufVxuLmNvbC02IHtcbiAgd2lkdGg6IDUwJTtcbn1cbi5jb2wtNSB7XG4gIHdpZHRoOiA0MS42NjY2NjY2NyU7XG59XG4uY29sLTQge1xuICB3aWR0aDogMzMuMzMzMzMzMzMlO1xufVxuLmNvbC0zIHtcbiAgd2lkdGg6IDI1JTtcbn1cbi5jb2wtMiB7XG4gIHdpZHRoOiAxNi42NjY2NjY2NyU7XG59XG4uY29sLTEge1xuICB3aWR0aDogOC4zMzMzMzMzMyU7XG59XG4uY29sLWF1dG8ge1xuICBmbGV4OiAwIDAgYXV0bztcbiAgbWF4LXdpZHRoOiBub25lO1xuICB3aWR0aDogYXV0bztcbn1cbi5jb2wtbXgtYXV0byB7XG4gIG1hcmdpbi1sZWZ0OiBhdXRvO1xuICBtYXJnaW4tcmlnaHQ6IGF1dG87XG59XG4uY29sLW1sLWF1dG8ge1xuICBtYXJnaW4tbGVmdDogYXV0bztcbn1cbi5jb2wtbXItYXV0byB7XG4gIG1hcmdpbi1yaWdodDogYXV0bztcbn1cbkBtZWRpYSAobWF4LXdpZHRoOiAkc2l6ZS14bCkge1xuICAuY29sLXhsLTEyLFxuICAuY29sLXhsLTExLFxuICAuY29sLXhsLTEwLFxuICAuY29sLXhsLTksXG4gIC5jb2wteGwtOCxcbiAgLmNvbC14bC03LFxuICAuY29sLXhsLTYsXG4gIC5jb2wteGwtNSxcbiAgLmNvbC14bC00LFxuICAuY29sLXhsLTMsXG4gIC5jb2wteGwtMixcbiAgLmNvbC14bC0xLFxuICAuY29sLXhsLWF1dG8ge1xuICAgIGZsZXg6IG5vbmU7XG4gIH1cbiAgLmNvbC14bC0xMiB7XG4gICAgd2lkdGg6IDEwMCU7XG4gIH1cbiAgLmNvbC14bC0xMSB7XG4gICAgd2lkdGg6IDkxLjY2NjY2NjY3JTtcbiAgfVxuICAuY29sLXhsLTEwIHtcbiAgICB3aWR0aDogODMuMzMzMzMzMzMlO1xuICB9XG4gIC5jb2wteGwtOSB7XG4gICAgd2lkdGg6IDc1JTtcbiAgfVxuICAuY29sLXhsLTgge1xuICAgIHdpZHRoOiA2Ni42NjY2NjY2NyU7XG4gIH1cbiAgLmNvbC14bC03IHtcbiAgICB3aWR0aDogNTguMzMzMzMzMzMlO1xuICB9XG4gIC5jb2wteGwtNiB7XG4gICAgd2lkdGg6IDUwJTtcbiAgfVxuICAuY29sLXhsLTUge1xuICAgIHdpZHRoOiA0MS42NjY2NjY2NyU7XG4gIH1cbiAgLmNvbC14bC00IHtcbiAgICB3aWR0aDogMzMuMzMzMzMzMzMlO1xuICB9XG4gIC5jb2wteGwtMyB7XG4gICAgd2lkdGg6IDI1JTtcbiAgfVxuICAuY29sLXhsLTIge1xuICAgIHdpZHRoOiAxNi42NjY2NjY2NyU7XG4gIH1cbiAgLmNvbC14bC0xIHtcbiAgICB3aWR0aDogOC4zMzMzMzMzMyU7XG4gIH1cbiAgLmNvbC14bC1hdXRvIHtcbiAgICB3aWR0aDogYXV0bztcbiAgfVxuICAuaGlkZS14bCB7XG4gICAgZGlzcGxheTogbm9uZSAhaW1wb3J0YW50O1xuICB9XG4gIC5zaG93LXhsIHtcbiAgICBkaXNwbGF5OiBibG9jayAhaW1wb3J0YW50O1xuICB9XG59XG5AbWVkaWEgKG1heC13aWR0aDogJHNpemUtbGcpIHtcbiAgLmNvbC1sZy0xMixcbiAgLmNvbC1sZy0xMSxcbiAgLmNvbC1sZy0xMCxcbiAgLmNvbC1sZy05LFxuICAuY29sLWxnLTgsXG4gIC5jb2wtbGctNyxcbiAgLmNvbC1sZy02LFxuICAuY29sLWxnLTUsXG4gIC5jb2wtbGctNCxcbiAgLmNvbC1sZy0zLFxuICAuY29sLWxnLTIsXG4gIC5jb2wtbGctMSxcbiAgLmNvbC1sZy1hdXRvIHtcbiAgICBmbGV4OiBub25lO1xuICB9XG4gIC5jb2wtbGctMTIge1xuICAgIHdpZHRoOiAxMDAlO1xuICB9XG4gIC5jb2wtbGctMTEge1xuICAgIHdpZHRoOiA5MS42NjY2NjY2NyU7XG4gIH1cbiAgLmNvbC1sZy0xMCB7XG4gICAgd2lkdGg6IDgzLjMzMzMzMzMzJTtcbiAgfVxuICAuY29sLWxnLTkge1xuICAgIHdpZHRoOiA3NSU7XG4gIH1cbiAgLmNvbC1sZy04IHtcbiAgICB3aWR0aDogNjYuNjY2NjY2NjclO1xuICB9XG4gIC5jb2wtbGctNyB7XG4gICAgd2lkdGg6IDU4LjMzMzMzMzMzJTtcbiAgfVxuICAuY29sLWxnLTYge1xuICAgIHdpZHRoOiA1MCU7XG4gIH1cbiAgLmNvbC1sZy01IHtcbiAgICB3aWR0aDogNDEuNjY2NjY2NjclO1xuICB9XG4gIC5jb2wtbGctNCB7XG4gICAgd2lkdGg6IDMzLjMzMzMzMzMzJTtcbiAgfVxuICAuY29sLWxnLTMge1xuICAgIHdpZHRoOiAyNSU7XG4gIH1cbiAgLmNvbC1sZy0yIHtcbiAgICB3aWR0aDogMTYuNjY2NjY2NjclO1xuICB9XG4gIC5jb2wtbGctMSB7XG4gICAgd2lkdGg6IDguMzMzMzMzMzMlO1xuICB9XG4gIC5jb2wtbGctYXV0byB7XG4gICAgd2lkdGg6IGF1dG87XG4gIH1cbiAgLmhpZGUtbGcge1xuICAgIGRpc3BsYXk6IG5vbmUgIWltcG9ydGFudDtcbiAgfVxuICAuc2hvdy1sZyB7XG4gICAgZGlzcGxheTogYmxvY2sgIWltcG9ydGFudDtcbiAgfVxufVxuQG1lZGlhIChtYXgtd2lkdGg6ICRzaXplLW1kKSB7XG4gIC5jb2wtbWQtMTIsXG4gIC5jb2wtbWQtMTEsXG4gIC5jb2wtbWQtMTAsXG4gIC5jb2wtbWQtOSxcbiAgLmNvbC1tZC04LFxuICAuY29sLW1kLTcsXG4gIC5jb2wtbWQtNixcbiAgLmNvbC1tZC01LFxuICAuY29sLW1kLTQsXG4gIC5jb2wtbWQtMyxcbiAgLmNvbC1tZC0yLFxuICAuY29sLW1kLTEsXG4gIC5jb2wtbWQtYXV0byB7XG4gICAgZmxleDogbm9uZTtcbiAgfVxuICAuY29sLW1kLTEyIHtcbiAgICB3aWR0aDogMTAwJTtcbiAgfVxuICAuY29sLW1kLTExIHtcbiAgICB3aWR0aDogOTEuNjY2NjY2NjclO1xuICB9XG4gIC5jb2wtbWQtMTAge1xuICAgIHdpZHRoOiA4My4zMzMzMzMzMyU7XG4gIH1cbiAgLmNvbC1tZC05IHtcbiAgICB3aWR0aDogNzUlO1xuICB9XG4gIC5jb2wtbWQtOCB7XG4gICAgd2lkdGg6IDY2LjY2NjY2NjY3JTtcbiAgfVxuICAuY29sLW1kLTcge1xuICAgIHdpZHRoOiA1OC4zMzMzMzMzMyU7XG4gIH1cbiAgLmNvbC1tZC02IHtcbiAgICB3aWR0aDogNTAlO1xuICB9XG4gIC5jb2wtbWQtNSB7XG4gICAgd2lkdGg6IDQxLjY2NjY2NjY3JTtcbiAgfVxuICAuY29sLW1kLTQge1xuICAgIHdpZHRoOiAzMy4zMzMzMzMzMyU7XG4gIH1cbiAgLmNvbC1tZC0zIHtcbiAgICB3aWR0aDogMjUlO1xuICB9XG4gIC5jb2wtbWQtMiB7XG4gICAgd2lkdGg6IDE2LjY2NjY2NjY3JTtcbiAgfVxuICAuY29sLW1kLTEge1xuICAgIHdpZHRoOiA4LjMzMzMzMzMzJTtcbiAgfVxuICAuY29sLW1kLWF1dG8ge1xuICAgIHdpZHRoOiBhdXRvO1xuICB9XG4gIC5oaWRlLW1kIHtcbiAgICBkaXNwbGF5OiBub25lICFpbXBvcnRhbnQ7XG4gIH1cbiAgLnNob3ctbWQge1xuICAgIGRpc3BsYXk6IGJsb2NrICFpbXBvcnRhbnQ7XG4gIH1cbn1cbkBtZWRpYSAobWF4LXdpZHRoOiAkc2l6ZS1zbSkge1xuICAuY29sLXNtLTEyLFxuICAuY29sLXNtLTExLFxuICAuY29sLXNtLTEwLFxuICAuY29sLXNtLTksXG4gIC5jb2wtc20tOCxcbiAgLmNvbC1zbS03LFxuICAuY29sLXNtLTYsXG4gIC5jb2wtc20tNSxcbiAgLmNvbC1zbS00LFxuICAuY29sLXNtLTMsXG4gIC5jb2wtc20tMixcbiAgLmNvbC1zbS0xLFxuICAuY29sLXNtLWF1dG8ge1xuICAgIGZsZXg6IG5vbmU7XG4gIH1cbiAgLmNvbC1zbS0xMiB7XG4gICAgd2lkdGg6IDEwMCU7XG4gIH1cbiAgLmNvbC1zbS0xMSB7XG4gICAgd2lkdGg6IDkxLjY2NjY2NjY3JTtcbiAgfVxuICAuY29sLXNtLTEwIHtcbiAgICB3aWR0aDogODMuMzMzMzMzMzMlO1xuICB9XG4gIC5jb2wtc20tOSB7XG4gICAgd2lkdGg6IDc1JTtcbiAgfVxuICAuY29sLXNtLTgge1xuICAgIHdpZHRoOiA2Ni42NjY2NjY2NyU7XG4gIH1cbiAgLmNvbC1zbS03IHtcbiAgICB3aWR0aDogNTguMzMzMzMzMzMlO1xuICB9XG4gIC5jb2wtc20tNiB7XG4gICAgd2lkdGg6IDUwJTtcbiAgfVxuICAuY29sLXNtLTUge1xuICAgIHdpZHRoOiA0MS42NjY2NjY2NyU7XG4gIH1cbiAgLmNvbC1zbS00IHtcbiAgICB3aWR0aDogMzMuMzMzMzMzMzMlO1xuICB9XG4gIC5jb2wtc20tMyB7XG4gICAgd2lkdGg6IDI1JTtcbiAgfVxuICAuY29sLXNtLTIge1xuICAgIHdpZHRoOiAxNi42NjY2NjY2NyU7XG4gIH1cbiAgLmNvbC1zbS0xIHtcbiAgICB3aWR0aDogOC4zMzMzMzMzMyU7XG4gIH1cbiAgLmNvbC1zbS1hdXRvIHtcbiAgICB3aWR0aDogYXV0bztcbiAgfVxuICAuaGlkZS1zbSB7XG4gICAgZGlzcGxheTogbm9uZSAhaW1wb3J0YW50O1xuICB9XG4gIC5zaG93LXNtIHtcbiAgICBkaXNwbGF5OiBibG9jayAhaW1wb3J0YW50O1xuICB9XG59XG5AbWVkaWEgKG1heC13aWR0aDogJHNpemUteHMpIHtcbiAgLmNvbC14cy0xMixcbiAgLmNvbC14cy0xMSxcbiAgLmNvbC14cy0xMCxcbiAgLmNvbC14cy05LFxuICAuY29sLXhzLTgsXG4gIC5jb2wteHMtNyxcbiAgLmNvbC14cy02LFxuICAuY29sLXhzLTUsXG4gIC5jb2wteHMtNCxcbiAgLmNvbC14cy0zLFxuICAuY29sLXhzLTIsXG4gIC5jb2wteHMtMSxcbiAgLmNvbC14cy1hdXRvIHtcbiAgICBmbGV4OiBub25lO1xuICB9XG4gIC5jb2wteHMtMTIge1xuICAgIHdpZHRoOiAxMDAlO1xuICB9XG4gIC5jb2wteHMtMTEge1xuICAgIHdpZHRoOiA5MS42NjY2NjY2NyU7XG4gIH1cbiAgLmNvbC14cy0xMCB7XG4gICAgd2lkdGg6IDgzLjMzMzMzMzMzJTtcbiAgfVxuICAuY29sLXhzLTkge1xuICAgIHdpZHRoOiA3NSU7XG4gIH1cbiAgLmNvbC14cy04IHtcbiAgICB3aWR0aDogNjYuNjY2NjY2NjclO1xuICB9XG4gIC5jb2wteHMtNyB7XG4gICAgd2lkdGg6IDU4LjMzMzMzMzMzJTtcbiAgfVxuICAuY29sLXhzLTYge1xuICAgIHdpZHRoOiA1MCU7XG4gIH1cbiAgLmNvbC14cy01IHtcbiAgICB3aWR0aDogNDEuNjY2NjY2NjclO1xuICB9XG4gIC5jb2wteHMtNCB7XG4gICAgd2lkdGg6IDMzLjMzMzMzMzMzJTtcbiAgfVxuICAuY29sLXhzLTMge1xuICAgIHdpZHRoOiAyNSU7XG4gIH1cbiAgLmNvbC14cy0yIHtcbiAgICB3aWR0aDogMTYuNjY2NjY2NjclO1xuICB9XG4gIC5jb2wteHMtMSB7XG4gICAgd2lkdGg6IDguMzMzMzMzMzMlO1xuICB9XG4gIC5jb2wteHMtYXV0byB7XG4gICAgd2lkdGg6IGF1dG87XG4gIH1cbiAgLmhpZGUteHMge1xuICAgIGRpc3BsYXk6IG5vbmUgIWltcG9ydGFudDtcbiAgfVxuICAuc2hvdy14cyB7XG4gICAgZGlzcGxheTogYmxvY2sgIWltcG9ydGFudDtcbiAgfVxufVxuIiwiLy8gSGVyb1xuLmhlcm8ge1xuICBkaXNwbGF5OiBmbGV4O1xuICBmbGV4LWRpcmVjdGlvbjogY29sdW1uO1xuICBqdXN0aWZ5LWNvbnRlbnQ6IHNwYWNlLWJldHdlZW47XG4gIHBhZGRpbmctYm90dG9tOiA0cmVtO1xuICBwYWRkaW5nLXRvcDogNHJlbTtcblxuICAmLmhlcm8tc20ge1xuICAgIHBhZGRpbmctYm90dG9tOiAycmVtO1xuICAgIHBhZGRpbmctdG9wOiAycmVtO1xuICB9XG5cbiAgJi5oZXJvLWxnIHtcbiAgICBwYWRkaW5nLWJvdHRvbTogOHJlbTtcbiAgICBwYWRkaW5nLXRvcDogOHJlbTtcbiAgfVxuXG4gIC5oZXJvLWJvZHkge1xuICAgIHBhZGRpbmc6ICRsYXlvdXQtc3BhY2luZztcbiAgfVxufSIsIi8vIE5hdmJhclxuLm5hdmJhciB7XG4gIGFsaWduLWl0ZW1zOiBzdHJldGNoO1xuICBkaXNwbGF5OiBmbGV4O1xuICBmbGV4LXdyYXA6IHdyYXA7XG4gIGp1c3RpZnktY29udGVudDogc3BhY2UtYmV0d2VlbjtcblxuICAubmF2YmFyLXNlY3Rpb24ge1xuICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7XG4gICAgZGlzcGxheTogZmxleDtcbiAgICBmbGV4OiAxIDAgMDtcblxuICAgICY6bm90KDpmaXJzdC1jaGlsZCk6bGFzdC1jaGlsZCB7XG4gICAgICBqdXN0aWZ5LWNvbnRlbnQ6IGZsZXgtZW5kO1xuICAgIH1cbiAgfVxuXG4gIC5uYXZiYXItY2VudGVyIHtcbiAgICBhbGlnbi1pdGVtczogY2VudGVyO1xuICAgIGRpc3BsYXk6IGZsZXg7XG4gICAgZmxleDogMCAwIGF1dG87XG4gIH1cblxuICAubmF2YmFyLWJyYW5kIHtcbiAgICBmb250LXNpemU6ICRmb250LXNpemUtbGc7XG4gICAgdGV4dC1kZWNvcmF0aW9uOiBub25lO1xuICB9XG59XG4iLCIvLyBBY2NvcmRpb25zXG4uYWNjb3JkaW9uIHtcbiAgaW5wdXQ6Y2hlY2tlZCB+LFxuICAmW29wZW5dIHtcbiAgICAmIC5hY2NvcmRpb24taGVhZGVyIHtcbiAgICAgIC5pY29uIHtcbiAgICAgICAgdHJhbnNmb3JtOiByb3RhdGUoOTBkZWcpO1xuICAgICAgfVxuICAgIH1cblxuICAgICYgLmFjY29yZGlvbi1ib2R5IHtcbiAgICAgIG1heC1oZWlnaHQ6IDUwcmVtO1xuICAgIH1cbiAgfVxuXG4gIC5hY2NvcmRpb24taGVhZGVyIHtcbiAgICBkaXNwbGF5OiBibG9jaztcbiAgICBwYWRkaW5nOiAkdW5pdC0xICR1bml0LTI7XG5cbiAgICAuaWNvbiB7XG4gICAgICB0cmFuc2l0aW9uOiB0cmFuc2Zvcm0gLjI1cztcbiAgICB9XG4gIH1cblxuICAuYWNjb3JkaW9uLWJvZHkge1xuICAgIG1hcmdpbi1ib3R0b206ICRsYXlvdXQtc3BhY2luZztcbiAgICBtYXgtaGVpZ2h0OiAwO1xuICAgIG92ZXJmbG93OiBoaWRkZW47XG4gICAgdHJhbnNpdGlvbjogbWF4LWhlaWdodCAuMjVzO1xuICB9XG59XG5cbi8vIFJlbW92ZSBkZWZhdWx0IGRldGFpbHMgbWFya2VyIGluIFdlYmtpdFxuc3VtbWFyeS5hY2NvcmRpb24taGVhZGVyIHtcbiAgJjo6LXdlYmtpdC1kZXRhaWxzLW1hcmtlciB7XG4gICAgZGlzcGxheTogbm9uZTtcbiAgfVxufVxuIiwiLy8gQXZhdGFyc1xuLmF2YXRhciB7XG4gIEBpbmNsdWRlIGF2YXRhci1iYXNlKCk7XG4gIGJhY2tncm91bmQ6ICRwcmltYXJ5LWNvbG9yO1xuICBib3JkZXItcmFkaXVzOiA1MCU7XG4gIGNvbG9yOiByZ2JhKCRsaWdodC1jb2xvciwgLjg1KTtcbiAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xuICBmb250LXdlaWdodDogMzAwO1xuICBsaW5lLWhlaWdodDogMS4yNTtcbiAgbWFyZ2luOiAwO1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gIHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7XG5cbiAgJi5hdmF0YXIteHMge1xuICAgIEBpbmNsdWRlIGF2YXRhci1iYXNlKCR1bml0LTQpO1xuICB9XG4gICYuYXZhdGFyLXNtIHtcbiAgICBAaW5jbHVkZSBhdmF0YXItYmFzZSgkdW5pdC02KTtcbiAgfVxuICAmLmF2YXRhci1sZyB7XG4gICAgQGluY2x1ZGUgYXZhdGFyLWJhc2UoJHVuaXQtMTIpO1xuICB9XG4gICYuYXZhdGFyLXhsIHtcbiAgICBAaW5jbHVkZSBhdmF0YXItYmFzZSgkdW5pdC0xNik7XG4gIH1cblxuICBpbWcge1xuICAgIGJvcmRlci1yYWRpdXM6IDUwJTtcbiAgICBoZWlnaHQ6IDEwMCU7XG4gICAgcG9zaXRpb246IHJlbGF0aXZlO1xuICAgIHdpZHRoOiAxMDAlO1xuICAgIHotaW5kZXg6ICR6aW5kZXgtMDtcbiAgfVxuXG4gIC5hdmF0YXItaWNvbixcbiAgLmF2YXRhci1wcmVzZW5jZSB7XG4gICAgYmFja2dyb3VuZDogJGJnLWNvbG9yLWxpZ2h0O1xuICAgIGJvdHRvbTogMTQuNjQlO1xuICAgIGhlaWdodDogNTAlO1xuICAgIHBhZGRpbmc6ICRib3JkZXItd2lkdGgtbGc7XG4gICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgIHJpZ2h0OiAxNC42NCU7XG4gICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUoNTAlLCA1MCUpO1xuICAgIHdpZHRoOiA1MCU7XG4gICAgei1pbmRleDogJHppbmRleC0wICsgMTtcbiAgfVxuXG4gIC5hdmF0YXItcHJlc2VuY2Uge1xuICAgIGJhY2tncm91bmQ6ICRncmF5LWNvbG9yO1xuICAgIGJveC1zaGFkb3c6IDAgMCAwICRib3JkZXItd2lkdGgtbGcgJGxpZ2h0LWNvbG9yO1xuICAgIGJvcmRlci1yYWRpdXM6IDUwJTtcbiAgICBoZWlnaHQ6IC41ZW07XG4gICAgd2lkdGg6IC41ZW07XG5cbiAgICAmLm9ubGluZSB7XG4gICAgICBiYWNrZ3JvdW5kOiAkc3VjY2Vzcy1jb2xvcjtcbiAgICB9XG5cbiAgICAmLmJ1c3kge1xuICAgICAgYmFja2dyb3VuZDogJGVycm9yLWNvbG9yO1xuICAgIH1cblxuICAgICYuYXdheSB7XG4gICAgICBiYWNrZ3JvdW5kOiAkd2FybmluZy1jb2xvcjtcbiAgICB9XG4gIH1cblxuICAmW2RhdGEtaW5pdGlhbF06OmJlZm9yZSB7XG4gICAgY29sb3I6IGN1cnJlbnRDb2xvcjtcbiAgICBjb250ZW50OiBhdHRyKGRhdGEtaW5pdGlhbCk7XG4gICAgbGVmdDogNTAlO1xuICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICB0b3A6IDUwJTtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtNTAlLCAtNTAlKTtcbiAgICB6LWluZGV4OiAkemluZGV4LTA7XG4gIH1cbn0iLCIvLyBCYWRnZXNcbi5iYWRnZSB7XG4gIHBvc2l0aW9uOiByZWxhdGl2ZTtcbiAgd2hpdGUtc3BhY2U6IG5vd3JhcDtcblxuICAmW2RhdGEtYmFkZ2VdLFxuICAmOm5vdChbZGF0YS1iYWRnZV0pIHtcbiAgICAmOjphZnRlciB7XG4gICAgICBiYWNrZ3JvdW5kOiAkcHJpbWFyeS1jb2xvcjtcbiAgICAgIGJhY2tncm91bmQtY2xpcDogcGFkZGluZy1ib3g7XG4gICAgICBib3JkZXItcmFkaXVzOiAuNXJlbTtcbiAgICAgIGJveC1zaGFkb3c6IDAgMCAwIC4xcmVtICRiZy1jb2xvci1saWdodDtcbiAgICAgIGNvbG9yOiAkbGlnaHQtY29sb3I7XG4gICAgICBjb250ZW50OiBhdHRyKGRhdGEtYmFkZ2UpO1xuICAgICAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xuICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUoLS4wNXJlbSwgLS41cmVtKTtcbiAgICB9XG4gIH1cbiAgJltkYXRhLWJhZGdlXSB7XG4gICAgJjo6YWZ0ZXIge1xuICAgICAgZm9udC1zaXplOiAkZm9udC1zaXplLXNtO1xuICAgICAgaGVpZ2h0OiAuOXJlbTtcbiAgICAgIGxpbmUtaGVpZ2h0OiAxO1xuICAgICAgbWluLXdpZHRoOiAuOXJlbTtcbiAgICAgIHBhZGRpbmc6IC4xcmVtIC4ycmVtO1xuICAgICAgdGV4dC1hbGlnbjogY2VudGVyO1xuICAgICAgd2hpdGUtc3BhY2U6IG5vd3JhcDtcbiAgICB9XG4gIH1cbiAgJjpub3QoW2RhdGEtYmFkZ2VdKSxcbiAgJltkYXRhLWJhZGdlPVwiXCJdIHtcbiAgICAmOjphZnRlciB7XG4gICAgICBoZWlnaHQ6IDZweDtcbiAgICAgIG1pbi13aWR0aDogNnB4O1xuICAgICAgcGFkZGluZzogMDtcbiAgICAgIHdpZHRoOiA2cHg7XG4gICAgfVxuICB9XG5cbiAgLy8gQmFkZ2VzIGZvciBCdXR0b25zXG4gICYuYnRuIHtcbiAgICAmOjphZnRlciB7XG4gICAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgICB0b3A6IDA7XG4gICAgICByaWdodDogMDtcbiAgICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlKDUwJSwgLTUwJSk7XG4gICAgfVxuICB9XG5cbiAgLy8gQmFkZ2VzIGZvciBBdmF0YXJzXG4gICYuYXZhdGFyIHtcbiAgICAmOjphZnRlciB7XG4gICAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgICB0b3A6IDE0LjY0JTtcbiAgICAgIHJpZ2h0OiAxNC42NCU7XG4gICAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSg1MCUsIC01MCUpO1xuICAgICAgei1pbmRleDogJHppbmRleC0xO1xuICAgIH1cbiAgfVxufVxuIiwiLy8gQnJlYWRjcnVtYnNcbi5icmVhZGNydW1iIHtcbiAgbGlzdC1zdHlsZTogbm9uZTtcbiAgbWFyZ2luOiAkdW5pdC0xIDA7XG4gIHBhZGRpbmc6ICR1bml0LTEgMDtcblxuICAuYnJlYWRjcnVtYi1pdGVtIHtcbiAgICBjb2xvcjogJGdyYXktY29sb3ItZGFyaztcbiAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XG4gICAgbWFyZ2luOiAwO1xuICAgIHBhZGRpbmc6ICR1bml0LTEgMDtcblxuICAgICY6bm90KDpsYXN0LWNoaWxkKSB7XG4gICAgICBtYXJnaW4tcmlnaHQ6ICR1bml0LTE7XG5cbiAgICAgIGEge1xuICAgICAgICBjb2xvcjogJGdyYXktY29sb3ItZGFyaztcbiAgICAgIH1cbiAgICB9XG5cbiAgICAmOm5vdCg6Zmlyc3QtY2hpbGQpIHtcbiAgICAgICY6OmJlZm9yZSB7XG4gICAgICAgIGNvbG9yOiAkZ3JheS1jb2xvci1kYXJrO1xuICAgICAgICBjb250ZW50OiBcIi9cIjtcbiAgICAgICAgcGFkZGluZy1yaWdodDogJHVuaXQtMjtcbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cbiIsIi8vIEJhcnNcbi5iYXIge1xuICBiYWNrZ3JvdW5kOiAkYmctY29sb3ItZGFyaztcbiAgYm9yZGVyLXJhZGl1czogJGJvcmRlci1yYWRpdXM7XG4gIGRpc3BsYXk6IGZsZXg7XG4gIGZsZXgtd3JhcDogbm93cmFwO1xuICBoZWlnaHQ6ICR1bml0LTQ7XG4gIHdpZHRoOiAxMDAlO1xuXG4gICYuYmFyLXNtIHtcbiAgICBoZWlnaHQ6ICR1bml0LTE7XG4gIH1cblxuICAvLyBUT0RPOiBhdHRyKCkgc3VwcG9ydFxuICAuYmFyLWl0ZW0ge1xuICAgIGJhY2tncm91bmQ6ICRwcmltYXJ5LWNvbG9yO1xuICAgIGNvbG9yOiAkbGlnaHQtY29sb3I7XG4gICAgZGlzcGxheTogYmxvY2s7XG4gICAgZm9udC1zaXplOiAkZm9udC1zaXplLXNtO1xuICAgIGZsZXgtc2hyaW5rOiAwO1xuICAgIGxpbmUtaGVpZ2h0OiAkdW5pdC00O1xuICAgIGhlaWdodDogMTAwJTtcbiAgICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gICAgdGV4dC1hbGlnbjogY2VudGVyO1xuICAgIHdpZHRoOiAwO1xuXG4gICAgJjpmaXJzdC1jaGlsZCB7XG4gICAgICBib3JkZXItYm90dG9tLWxlZnQtcmFkaXVzOiAkYm9yZGVyLXJhZGl1cztcbiAgICAgIGJvcmRlci10b3AtbGVmdC1yYWRpdXM6ICRib3JkZXItcmFkaXVzO1xuICAgIH1cbiAgICAmOmxhc3QtY2hpbGQge1xuICAgICAgYm9yZGVyLWJvdHRvbS1yaWdodC1yYWRpdXM6ICRib3JkZXItcmFkaXVzO1xuICAgICAgYm9yZGVyLXRvcC1yaWdodC1yYWRpdXM6ICRib3JkZXItcmFkaXVzO1xuICAgICAgZmxleC1zaHJpbms6IDE7XG4gICAgfVxuICB9XG59XG5cbi8vIFNsaWRlciBiYXJcbi5iYXItc2xpZGVyIHtcbiAgaGVpZ2h0OiAkYm9yZGVyLXdpZHRoLWxnO1xuICBtYXJnaW46ICRsYXlvdXQtc3BhY2luZyAwO1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG5cbiAgLmJhci1pdGVtIHtcbiAgICBsZWZ0OiAwO1xuICAgIHBhZGRpbmc6IDA7XG4gICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgICY6bm90KDpsYXN0LWNoaWxkKTpmaXJzdC1jaGlsZCB7XG4gICAgICBiYWNrZ3JvdW5kOiAkYmctY29sb3ItZGFyaztcbiAgICAgIHotaW5kZXg6ICR6aW5kZXgtMDtcbiAgICB9XG4gIH1cblxuICAuYmFyLXNsaWRlci1idG4ge1xuICAgIGJhY2tncm91bmQ6ICRwcmltYXJ5LWNvbG9yO1xuICAgIGJvcmRlcjogMDtcbiAgICBib3JkZXItcmFkaXVzOiA1MCU7XG4gICAgaGVpZ2h0OiAkdW5pdC0zO1xuICAgIHBhZGRpbmc6IDA7XG4gICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgIHJpZ2h0OiAwO1xuICAgIHRvcDogNTAlO1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlKDUwJSwgLTUwJSk7XG4gICAgd2lkdGg6ICR1bml0LTM7XG5cbiAgICAmOmFjdGl2ZSB7XG4gICAgICBib3gtc2hhZG93OiAwIDAgMCAuMXJlbSAkcHJpbWFyeS1jb2xvcjtcbiAgICB9XG4gIH1cbn1cbiIsIi8vIENhcmRzXG4uY2FyZCB7XG4gIGJhY2tncm91bmQ6ICRiZy1jb2xvci1saWdodDtcbiAgYm9yZGVyOiAkYm9yZGVyLXdpZHRoIHNvbGlkICRib3JkZXItY29sb3I7XG4gIGJvcmRlci1yYWRpdXM6ICRib3JkZXItcmFkaXVzO1xuICBkaXNwbGF5OiBmbGV4O1xuICBmbGV4LWRpcmVjdGlvbjogY29sdW1uO1xuXG4gIC5jYXJkLWhlYWRlcixcbiAgLmNhcmQtYm9keSxcbiAgLmNhcmQtZm9vdGVyIHtcbiAgICBwYWRkaW5nOiAkbGF5b3V0LXNwYWNpbmctbGc7XG4gICAgcGFkZGluZy1ib3R0b206IDA7XG5cbiAgICAmOmxhc3QtY2hpbGQge1xuICAgICAgcGFkZGluZy1ib3R0b206ICRsYXlvdXQtc3BhY2luZy1sZztcbiAgICB9XG4gIH1cblxuICAuY2FyZC1ib2R5IHtcbiAgICBmbGV4OiAxIDEgYXV0bztcbiAgfVxuXG4gIC5jYXJkLWltYWdlIHtcbiAgICBwYWRkaW5nLXRvcDogJGxheW91dC1zcGFjaW5nLWxnO1xuXG4gICAgJjpmaXJzdC1jaGlsZCB7XG4gICAgICBwYWRkaW5nLXRvcDogMDtcblxuICAgICAgaW1nIHtcbiAgICAgICAgYm9yZGVyLXRvcC1sZWZ0LXJhZGl1czogJGJvcmRlci1yYWRpdXM7XG4gICAgICAgIGJvcmRlci10b3AtcmlnaHQtcmFkaXVzOiAkYm9yZGVyLXJhZGl1cztcbiAgICAgIH1cbiAgICB9XG5cbiAgICAmOmxhc3QtY2hpbGQge1xuICAgICAgaW1nIHtcbiAgICAgICAgYm9yZGVyLWJvdHRvbS1sZWZ0LXJhZGl1czogJGJvcmRlci1yYWRpdXM7XG4gICAgICAgIGJvcmRlci1ib3R0b20tcmlnaHQtcmFkaXVzOiAkYm9yZGVyLXJhZGl1cztcbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cbiIsIi8vIENoaXBzXG4uY2hpcCB7XG4gIGFsaWduLWl0ZW1zOiBjZW50ZXI7XG4gIGJhY2tncm91bmQ6ICRiZy1jb2xvci1kYXJrO1xuICBib3JkZXItcmFkaXVzOiA1cmVtO1xuICBkaXNwbGF5OiBpbmxpbmUtZmxleDtcbiAgZm9udC1zaXplOiA5MCU7XG4gIGhlaWdodDogJHVuaXQtNjtcbiAgbGluZS1oZWlnaHQ6ICR1bml0LTQ7XG4gIG1hcmdpbjogJHVuaXQtaDtcbiAgbWF4LXdpZHRoOiAkY29udHJvbC13aWR0aC1zbTtcbiAgb3ZlcmZsb3c6IGhpZGRlbjtcbiAgcGFkZGluZzogJHVuaXQtMSAkdW5pdC0yO1xuICB0ZXh0LWRlY29yYXRpb246IG5vbmU7XG4gIHRleHQtb3ZlcmZsb3c6IGVsbGlwc2lzO1xuICB2ZXJ0aWNhbC1hbGlnbjogbWlkZGxlO1xuICB3aGl0ZS1zcGFjZTogbm93cmFwO1xuXG4gICYuYWN0aXZlIHtcbiAgICBiYWNrZ3JvdW5kOiAkcHJpbWFyeS1jb2xvcjtcbiAgICBjb2xvcjogJGxpZ2h0LWNvbG9yO1xuICB9XG5cbiAgLmF2YXRhciB7XG4gICAgbWFyZ2luLWxlZnQ6IC0kdW5pdC0yO1xuICAgIG1hcmdpbi1yaWdodDogJHVuaXQtMTtcbiAgfVxuXG4gIC5idG4tY2xlYXIge1xuICAgIGJvcmRlci1yYWRpdXM6IDUwJTtcbiAgICB0cmFuc2Zvcm06IHNjYWxlKC43NSk7XG4gIH1cbn1cbiIsIi8vIERyb3Bkb3duXG4uZHJvcGRvd24ge1xuICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XG4gIHBvc2l0aW9uOiByZWxhdGl2ZTtcblxuICAubWVudSB7XG4gICAgYW5pbWF0aW9uOiBzbGlkZS1kb3duIC4xNXMgZWFzZSAxO1xuICAgIGRpc3BsYXk6IG5vbmU7XG4gICAgbGVmdDogMDtcbiAgICBtYXgtaGVpZ2h0OiA1MHZoO1xuICAgIG92ZXJmbG93LXk6IGF1dG87XG4gICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgIHRvcDogMTAwJTtcbiAgfVxuXG4gICYuZHJvcGRvd24tcmlnaHQge1xuICAgIC5tZW51IHtcbiAgICAgIGxlZnQ6IGF1dG87XG4gICAgICByaWdodDogMDtcbiAgICB9XG4gIH1cblxuICAmLmFjdGl2ZSAubWVudSxcbiAgLmRyb3Bkb3duLXRvZ2dsZTpmb2N1cyArIC5tZW51LFxuICAubWVudTpob3ZlciB7XG4gICAgZGlzcGxheTogYmxvY2s7XG4gIH1cblxuICAvLyBGaXggZHJvcGRvd24tdG9nZ2xlIGJvcmRlciByYWRpdXMgaW4gYnV0dG9uIGdyb3Vwc1xuICAuYnRuLWdyb3VwIHtcbiAgICAuZHJvcGRvd24tdG9nZ2xlOm50aC1sYXN0LWNoaWxkKDIpIHtcbiAgICAgIGJvcmRlci1ib3R0b20tcmlnaHQtcmFkaXVzOiAkYm9yZGVyLXJhZGl1cztcbiAgICAgIGJvcmRlci10b3AtcmlnaHQtcmFkaXVzOiAkYm9yZGVyLXJhZGl1cztcbiAgICB9XG4gIH1cbn1cbiIsIi8vIEVtcHR5IHN0YXRlcyAob3IgQmxhbmsgc2xhdGVzKVxuLmVtcHR5IHtcbiAgYmFja2dyb3VuZDogJGJnLWNvbG9yO1xuICBib3JkZXItcmFkaXVzOiAkYm9yZGVyLXJhZGl1cztcbiAgY29sb3I6ICRncmF5LWNvbG9yLWRhcms7XG4gIHRleHQtYWxpZ246IGNlbnRlcjtcbiAgcGFkZGluZzogJHVuaXQtMTYgJHVuaXQtODtcblxuICAuZW1wdHktaWNvbiB7XG4gICAgbWFyZ2luLWJvdHRvbTogJGxheW91dC1zcGFjaW5nLWxnO1xuICB9XG5cbiAgLmVtcHR5LXRpdGxlLFxuICAuZW1wdHktc3VidGl0bGUge1xuICAgIG1hcmdpbjogJGxheW91dC1zcGFjaW5nIGF1dG87XG4gIH1cblxuICAuZW1wdHktYWN0aW9uIHtcbiAgICBtYXJnaW4tdG9wOiAkbGF5b3V0LXNwYWNpbmctbGc7XG4gIH1cbn1cbiIsIi8vIE1lbnVzXG4ubWVudSB7XG4gIEBpbmNsdWRlIHNoYWRvdy12YXJpYW50KC4wNXJlbSk7XG4gIGJhY2tncm91bmQ6ICRiZy1jb2xvci1saWdodDtcbiAgYm9yZGVyLXJhZGl1czogJGJvcmRlci1yYWRpdXM7XG4gIGxpc3Qtc3R5bGU6IG5vbmU7XG4gIG1hcmdpbjogMDtcbiAgbWluLXdpZHRoOiAkY29udHJvbC13aWR0aC14cztcbiAgcGFkZGluZzogJHVuaXQtMjtcbiAgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKCRsYXlvdXQtc3BhY2luZy1zbSk7XG4gIHotaW5kZXg6ICR6aW5kZXgtMztcblxuICAmLm1lbnUtbmF2IHtcbiAgICBiYWNrZ3JvdW5kOiB0cmFuc3BhcmVudDtcbiAgICBib3gtc2hhZG93OiBub25lO1xuICB9XG5cbiAgLm1lbnUtaXRlbSB7XG4gICAgbWFyZ2luLXRvcDogMDtcbiAgICBwYWRkaW5nOiAwICR1bml0LTI7XG4gICAgcG9zaXRpb246IHJlbGF0aXZlO1xuICAgIHRleHQtZGVjb3JhdGlvbjogbm9uZTtcblxuICAgICYgPiBhIHtcbiAgICAgIGJvcmRlci1yYWRpdXM6ICRib3JkZXItcmFkaXVzO1xuICAgICAgY29sb3I6IGluaGVyaXQ7XG4gICAgICBkaXNwbGF5OiBibG9jaztcbiAgICAgIG1hcmdpbjogMCAoLSR1bml0LTIpO1xuICAgICAgcGFkZGluZzogJHVuaXQtMSAkdW5pdC0yO1xuICAgICAgdGV4dC1kZWNvcmF0aW9uOiBub25lO1xuICAgICAgJjpmb2N1cyxcbiAgICAgICY6aG92ZXIge1xuICAgICAgICBiYWNrZ3JvdW5kOiAkc2Vjb25kYXJ5LWNvbG9yO1xuICAgICAgICBjb2xvcjogJHByaW1hcnktY29sb3I7XG4gICAgICB9XG4gICAgICAmOmFjdGl2ZSxcbiAgICAgICYuYWN0aXZlIHtcbiAgICAgICAgYmFja2dyb3VuZDogJHNlY29uZGFyeS1jb2xvcjtcbiAgICAgICAgY29sb3I6ICRwcmltYXJ5LWNvbG9yO1xuICAgICAgfVxuICAgIH1cblxuICAgIC5mb3JtLWNoZWNrYm94LFxuICAgIC5mb3JtLXJhZGlvLFxuICAgIC5mb3JtLXN3aXRjaCB7XG4gICAgICBtYXJnaW46ICR1bml0LWggMDtcbiAgICB9XG5cbiAgICAmICsgLm1lbnUtaXRlbSB7XG4gICAgICBtYXJnaW4tdG9wOiAkdW5pdC0xO1xuICAgIH1cbiAgfVxuXG4gIC5tZW51LWJhZGdlIHtcbiAgICBhbGlnbi1pdGVtczogY2VudGVyO1xuICAgIGRpc3BsYXk6IGZsZXg7XG4gICAgaGVpZ2h0OiAxMDAlO1xuICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICByaWdodDogMDtcbiAgICB0b3A6IDA7XG5cbiAgICAubGFiZWwge1xuICAgICAgbWFyZ2luLXJpZ2h0OiAkdW5pdC0yO1xuICAgIH1cbiAgfVxufSIsIi8vIE1vZGFsc1xuLm1vZGFsIHtcbiAgYWxpZ24taXRlbXM6IGNlbnRlcjtcbiAgYm90dG9tOiAwO1xuICBkaXNwbGF5OiBub25lO1xuICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjtcbiAgbGVmdDogMDtcbiAgb3BhY2l0eTogMDtcbiAgb3ZlcmZsb3c6IGhpZGRlbjtcbiAgcGFkZGluZzogJGxheW91dC1zcGFjaW5nO1xuICBwb3NpdGlvbjogZml4ZWQ7XG4gIHJpZ2h0OiAwO1xuICB0b3A6IDA7XG5cbiAgJjp0YXJnZXQsXG4gICYuYWN0aXZlIHtcbiAgICBkaXNwbGF5OiBmbGV4O1xuICAgIG9wYWNpdHk6IDE7XG4gICAgei1pbmRleDogJHppbmRleC00O1xuXG4gICAgLm1vZGFsLW92ZXJsYXkge1xuICAgICAgYmFja2dyb3VuZDogcmdiYSgkYmctY29sb3IsIC43NSk7XG4gICAgICBib3R0b206IDA7XG4gICAgICBjdXJzb3I6IGRlZmF1bHQ7XG4gICAgICBkaXNwbGF5OiBibG9jaztcbiAgICAgIGxlZnQ6IDA7XG4gICAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgICByaWdodDogMDtcbiAgICAgIHRvcDogMDtcbiAgICB9XG5cbiAgICAubW9kYWwtY29udGFpbmVyIHtcbiAgICAgIGFuaW1hdGlvbjogc2xpZGUtZG93biAuMnMgZWFzZSAxO1xuICAgICAgei1pbmRleDogJHppbmRleC0wO1xuICAgIH1cbiAgfVxuXG4gICYubW9kYWwtc20ge1xuICAgIC5tb2RhbC1jb250YWluZXIge1xuICAgICAgbWF4LXdpZHRoOiAkY29udHJvbC13aWR0aC1zbTtcbiAgICAgIHBhZGRpbmc6IDAgJHVuaXQtMjtcbiAgICB9XG4gIH1cblxuICAmLm1vZGFsLWxnIHtcbiAgICAubW9kYWwtb3ZlcmxheSB7XG4gICAgICBiYWNrZ3JvdW5kOiAkYmctY29sb3ItbGlnaHQ7XG4gICAgfVxuXG4gICAgLm1vZGFsLWNvbnRhaW5lciB7XG4gICAgICBib3gtc2hhZG93OiBub25lO1xuICAgICAgbWF4LXdpZHRoOiAkY29udHJvbC13aWR0aC1sZztcbiAgICB9XG4gIH1cbn1cblxuLm1vZGFsLWNvbnRhaW5lciB7XG4gIEBpbmNsdWRlIHNoYWRvdy12YXJpYW50KC4ycmVtKTtcbiAgYmFja2dyb3VuZDogJGJnLWNvbG9yLWxpZ2h0O1xuICBib3JkZXItcmFkaXVzOiAkYm9yZGVyLXJhZGl1cztcbiAgZGlzcGxheTogZmxleDtcbiAgZmxleC1kaXJlY3Rpb246IGNvbHVtbjtcbiAgbWF4LWhlaWdodDogNzV2aDtcbiAgbWF4LXdpZHRoOiAkY29udHJvbC13aWR0aC1tZDtcbiAgcGFkZGluZzogMCAkdW5pdC00O1xuICB3aWR0aDogMTAwJTtcblxuICAmLm1vZGFsLWZ1bGxoZWlnaHQge1xuICAgIG1heC1oZWlnaHQ6IDEwMHZoO1xuICB9XG5cbiAgLm1vZGFsLWhlYWRlciB7XG4gICAgY29sb3I6ICRkYXJrLWNvbG9yO1xuICAgIHBhZGRpbmc6ICR1bml0LTQ7XG4gIH1cblxuICAubW9kYWwtYm9keSB7XG4gICAgb3ZlcmZsb3cteTogYXV0bztcbiAgICBwYWRkaW5nOiAkdW5pdC00O1xuICAgIHBvc2l0aW9uOiByZWxhdGl2ZTtcbiAgfVxuXG4gIC5tb2RhbC1mb290ZXIge1xuICAgIHBhZGRpbmc6ICR1bml0LTQ7XG4gICAgdGV4dC1hbGlnbjogcmlnaHQ7XG4gIH1cbn1cbiIsIi8vIE5hdnNcbi5uYXYge1xuICBkaXNwbGF5OiBmbGV4O1xuICBmbGV4LWRpcmVjdGlvbjogY29sdW1uO1xuICBsaXN0LXN0eWxlOiBub25lO1xuICBtYXJnaW46ICR1bml0LTEgMDtcblxuICAubmF2LWl0ZW0ge1xuICAgIGEge1xuICAgICAgY29sb3I6ICRncmF5LWNvbG9yLWRhcms7XG4gICAgICBwYWRkaW5nOiAkdW5pdC0xICR1bml0LTI7XG4gICAgICB0ZXh0LWRlY29yYXRpb246IG5vbmU7XG4gICAgICAmOmZvY3VzLFxuICAgICAgJjpob3ZlciB7XG4gICAgICAgIGNvbG9yOiAkcHJpbWFyeS1jb2xvcjtcbiAgICAgIH1cbiAgICB9XG4gICAgJi5hY3RpdmUge1xuICAgICAgJiA+IGEge1xuICAgICAgICBjb2xvcjogZGFya2VuKCRncmF5LWNvbG9yLWRhcmssIDEwJSk7XG4gICAgICAgIGZvbnQtd2VpZ2h0OiBib2xkO1xuICAgICAgICAmOmZvY3VzLFxuICAgICAgICAmOmhvdmVyIHtcbiAgICAgICAgICBjb2xvcjogJHByaW1hcnktY29sb3I7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICAmIC5uYXYge1xuICAgIG1hcmdpbi1ib3R0b206ICR1bml0LTI7XG4gICAgbWFyZ2luLWxlZnQ6ICR1bml0LTQ7XG4gIH1cbn1cbiIsIi8vIFBhZ2luYXRpb25cbi5wYWdpbmF0aW9uIHtcbiAgZGlzcGxheTogZmxleDtcbiAgbGlzdC1zdHlsZTogbm9uZTtcbiAgbWFyZ2luOiAkdW5pdC0xIDA7XG4gIHBhZGRpbmc6ICR1bml0LTEgMDtcblxuICAucGFnZS1pdGVtIHtcbiAgICBtYXJnaW46ICR1bml0LTEgJHVuaXQtbztcblxuICAgIHNwYW4ge1xuICAgICAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xuICAgICAgcGFkZGluZzogJHVuaXQtMSAkdW5pdC0xO1xuICAgIH1cblxuICAgIGEge1xuICAgICAgYm9yZGVyLXJhZGl1czogJGJvcmRlci1yYWRpdXM7XG4gICAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XG4gICAgICBwYWRkaW5nOiAkdW5pdC0xICR1bml0LTI7XG4gICAgICB0ZXh0LWRlY29yYXRpb246IG5vbmU7XG4gICAgICAmOmZvY3VzLFxuICAgICAgJjpob3ZlciB7XG4gICAgICAgIGNvbG9yOiAkcHJpbWFyeS1jb2xvcjtcbiAgICAgIH1cbiAgICB9XG5cbiAgICAmLmRpc2FibGVkIHtcbiAgICAgIGEge1xuICAgICAgICBjdXJzb3I6IGRlZmF1bHQ7XG4gICAgICAgIG9wYWNpdHk6IC41O1xuICAgICAgICBwb2ludGVyLWV2ZW50czogbm9uZTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICAmLmFjdGl2ZSB7XG4gICAgICBhIHtcbiAgICAgICAgYmFja2dyb3VuZDogJHByaW1hcnktY29sb3I7XG4gICAgICAgIGNvbG9yOiAkbGlnaHQtY29sb3I7XG4gICAgICB9XG4gICAgfVxuXG4gICAgJi5wYWdlLXByZXYsXG4gICAgJi5wYWdlLW5leHQge1xuICAgICAgZmxleDogMSAwIDUwJTtcbiAgICB9XG5cbiAgICAmLnBhZ2UtbmV4dCB7XG4gICAgICB0ZXh0LWFsaWduOiByaWdodDtcbiAgICB9XG5cbiAgICAucGFnZS1pdGVtLXRpdGxlIHtcbiAgICAgIG1hcmdpbjogMDtcbiAgICB9XG5cbiAgICAucGFnZS1pdGVtLXN1YnRpdGxlIHtcbiAgICAgIG1hcmdpbjogMDtcbiAgICAgIG9wYWNpdHk6IC41O1xuICAgIH1cbiAgfVxufVxuIiwiLy8gUGFuZWxzXG4ucGFuZWwge1xuICBib3JkZXI6ICRib3JkZXItd2lkdGggc29saWQgJGJvcmRlci1jb2xvcjtcbiAgYm9yZGVyLXJhZGl1czogJGJvcmRlci1yYWRpdXM7XG4gIGRpc3BsYXk6IGZsZXg7XG4gIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47XG5cbiAgLnBhbmVsLWhlYWRlcixcbiAgLnBhbmVsLWZvb3RlciB7XG4gICAgZmxleDogMCAwIGF1dG87XG4gICAgcGFkZGluZzogJGxheW91dC1zcGFjaW5nLWxnO1xuICB9XG5cbiAgLnBhbmVsLW5hdiB7XG4gICAgZmxleDogMCAwIGF1dG87XG4gIH1cblxuICAucGFuZWwtYm9keSB7XG4gICAgZmxleDogMSAxIGF1dG87XG4gICAgb3ZlcmZsb3cteTogYXV0bztcbiAgICBwYWRkaW5nOiAwICRsYXlvdXQtc3BhY2luZy1sZztcbiAgfVxufVxuIiwiLy8gUG9wb3ZlcnNcbi5wb3BvdmVyIHtcbiAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG5cbiAgLnBvcG92ZXItY29udGFpbmVyIHtcbiAgICBsZWZ0OiA1MCU7XG4gICAgb3BhY2l0eTogMDtcbiAgICBwYWRkaW5nOiAkbGF5b3V0LXNwYWNpbmc7XG4gICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgIHRvcDogMDtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtNTAlLCAtNTAlKSBzY2FsZSgwKTtcbiAgICB0cmFuc2l0aW9uOiB0cmFuc2Zvcm0gLjJzO1xuICAgIHdpZHRoOiAkY29udHJvbC13aWR0aC1zbTtcbiAgICB6LWluZGV4OiAkemluZGV4LTM7XG4gIH1cblxuICAqOmZvY3VzICsgLnBvcG92ZXItY29udGFpbmVyLFxuICAmOmhvdmVyIC5wb3BvdmVyLWNvbnRhaW5lciB7XG4gICAgZGlzcGxheTogYmxvY2s7XG4gICAgb3BhY2l0eTogMTtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtNTAlLCAtMTAwJSkgc2NhbGUoMSk7XG4gIH1cblxuICAmLnBvcG92ZXItcmlnaHQge1xuICAgIC5wb3BvdmVyLWNvbnRhaW5lciB7XG4gICAgICBsZWZ0OiAxMDAlO1xuICAgICAgdG9wOiA1MCU7XG4gICAgfVxuXG4gICAgKjpmb2N1cyArIC5wb3BvdmVyLWNvbnRhaW5lcixcbiAgICAmOmhvdmVyIC5wb3BvdmVyLWNvbnRhaW5lciB7XG4gICAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgwLCAtNTAlKSBzY2FsZSgxKTtcbiAgICB9XG4gIH1cblxuICAmLnBvcG92ZXItYm90dG9tIHtcbiAgICAucG9wb3Zlci1jb250YWluZXIge1xuICAgICAgbGVmdDogNTAlO1xuICAgICAgdG9wOiAxMDAlO1xuICAgIH1cblxuICAgICo6Zm9jdXMgKyAucG9wb3Zlci1jb250YWluZXIsXG4gICAgJjpob3ZlciAucG9wb3Zlci1jb250YWluZXIge1xuICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUoLTUwJSwgMCkgc2NhbGUoMSk7XG4gICAgfVxuICB9XG5cbiAgJi5wb3BvdmVyLWxlZnQge1xuICAgIC5wb3BvdmVyLWNvbnRhaW5lciB7XG4gICAgICBsZWZ0OiAwO1xuICAgICAgdG9wOiA1MCU7XG4gICAgfVxuXG4gICAgKjpmb2N1cyArIC5wb3BvdmVyLWNvbnRhaW5lcixcbiAgICAmOmhvdmVyIC5wb3BvdmVyLWNvbnRhaW5lciB7XG4gICAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtMTAwJSwgLTUwJSkgc2NhbGUoMSk7XG4gICAgfVxuICB9XG5cbiAgLmNhcmQge1xuICAgIEBpbmNsdWRlIHNoYWRvdy12YXJpYW50KC4ycmVtKTtcbiAgICBib3JkZXI6IDA7XG4gIH1cbn1cbiIsIi8vIFN0ZXBzXG4uc3RlcCB7XG4gIGRpc3BsYXk6IGZsZXg7XG4gIGZsZXgtd3JhcDogbm93cmFwO1xuICBsaXN0LXN0eWxlOiBub25lO1xuICBtYXJnaW46ICR1bml0LTEgMDtcbiAgd2lkdGg6IDEwMCU7XG5cbiAgLnN0ZXAtaXRlbSB7XG4gICAgZmxleDogMSAxIDA7XG4gICAgbWFyZ2luLXRvcDogMDtcbiAgICBtaW4taGVpZ2h0OiAxcmVtO1xuICAgIHRleHQtYWxpZ246IGNlbnRlcjtcbiAgICBwb3NpdGlvbjogcmVsYXRpdmU7XG5cbiAgICAmOm5vdCg6Zmlyc3QtY2hpbGQpOjpiZWZvcmUge1xuICAgICAgYmFja2dyb3VuZDogJHByaW1hcnktY29sb3I7XG4gICAgICBjb250ZW50OiBcIlwiO1xuICAgICAgaGVpZ2h0OiAycHg7XG4gICAgICBsZWZ0OiAtNTAlO1xuICAgICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgICAgdG9wOiA5cHg7XG4gICAgICB3aWR0aDogMTAwJTtcbiAgICB9XG5cbiAgICBhIHtcbiAgICAgIGNvbG9yOiAkcHJpbWFyeS1jb2xvcjtcbiAgICAgIGRpc3BsYXk6IGlubGluZS1ibG9jaztcbiAgICAgIHBhZGRpbmc6IDIwcHggMTBweCAwO1xuICAgICAgdGV4dC1kZWNvcmF0aW9uOiBub25lO1xuXG4gICAgICAmOjpiZWZvcmUge1xuICAgICAgICBiYWNrZ3JvdW5kOiAkcHJpbWFyeS1jb2xvcjtcbiAgICAgICAgYm9yZGVyOiAkYm9yZGVyLXdpZHRoLWxnIHNvbGlkICRsaWdodC1jb2xvcjtcbiAgICAgICAgYm9yZGVyLXJhZGl1czogNTAlO1xuICAgICAgICBjb250ZW50OiBcIlwiO1xuICAgICAgICBkaXNwbGF5OiBibG9jaztcbiAgICAgICAgaGVpZ2h0OiAkdW5pdC0zO1xuICAgICAgICBsZWZ0OiA1MCU7XG4gICAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICAgICAgdG9wOiAkdW5pdC0xO1xuICAgICAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVgoLTUwJSk7XG4gICAgICAgIHdpZHRoOiAkdW5pdC0zO1xuICAgICAgICB6LWluZGV4OiAkemluZGV4LTA7XG4gICAgICB9XG4gICAgfVxuXG4gICAgJi5hY3RpdmUge1xuICAgICAgYSB7XG4gICAgICAgICY6OmJlZm9yZSB7XG4gICAgICAgICAgYmFja2dyb3VuZDogJGxpZ2h0LWNvbG9yO1xuICAgICAgICAgIGJvcmRlcjogJGJvcmRlci13aWR0aC1sZyBzb2xpZCAkcHJpbWFyeS1jb2xvcjtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICAmIH4gLnN0ZXAtaXRlbSB7XG4gICAgICAgICY6OmJlZm9yZSB7XG4gICAgICAgICAgYmFja2dyb3VuZDogJGJvcmRlci1jb2xvcjtcbiAgICAgICAgfVxuXG4gICAgICAgIGEge1xuICAgICAgICAgIGNvbG9yOiAkZ3JheS1jb2xvcjtcblxuICAgICAgICAgICY6OmJlZm9yZSB7XG4gICAgICAgICAgICBiYWNrZ3JvdW5kOiAkYm9yZGVyLWNvbG9yO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfVxufVxuIiwiLy8gVGFic1xuLnRhYiB7XG4gIGFsaWduLWl0ZW1zOiBjZW50ZXI7XG4gIGJvcmRlci1ib3R0b206ICRib3JkZXItd2lkdGggc29saWQgJGJvcmRlci1jb2xvcjtcbiAgZGlzcGxheTogZmxleDtcbiAgZmxleC13cmFwOiB3cmFwO1xuICBsaXN0LXN0eWxlOiBub25lO1xuICBtYXJnaW46ICR1bml0LTEgMCAoJHVuaXQtMSAtICRib3JkZXItd2lkdGgpIDA7XG5cbiAgLnRhYi1pdGVtIHtcbiAgICBtYXJnaW4tdG9wOiAwO1xuXG4gICAgYSB7XG4gICAgICBib3JkZXItYm90dG9tOiAkYm9yZGVyLXdpZHRoLWxnIHNvbGlkIHRyYW5zcGFyZW50O1xuICAgICAgY29sb3I6IGluaGVyaXQ7XG4gICAgICBkaXNwbGF5OiBibG9jaztcbiAgICAgIG1hcmdpbjogMCAkdW5pdC0yIDAgMDtcbiAgICAgIHBhZGRpbmc6ICR1bml0LTIgJHVuaXQtMSAkdW5pdC0yIC0gJGJvcmRlci13aWR0aC1sZyAkdW5pdC0xO1xuICAgICAgdGV4dC1kZWNvcmF0aW9uOiBub25lO1xuICAgICAgJjpmb2N1cyxcbiAgICAgICY6aG92ZXIge1xuICAgICAgICBjb2xvcjogJGxpbmstY29sb3I7XG4gICAgICB9XG4gICAgfVxuICAgICYuYWN0aXZlIGEsXG4gICAgYS5hY3RpdmUge1xuICAgICAgYm9yZGVyLWJvdHRvbS1jb2xvcjogJHByaW1hcnktY29sb3I7XG4gICAgICBjb2xvcjogJGxpbmstY29sb3I7XG4gICAgfVxuXG4gICAgJi50YWItYWN0aW9uIHtcbiAgICAgIGZsZXg6IDEgMCBhdXRvO1xuICAgICAgdGV4dC1hbGlnbjogcmlnaHQ7XG4gICAgfVxuXG4gICAgLmJ0bi1jbGVhciB7XG4gICAgICBtYXJnaW4tdG9wOiAtJHVuaXQtMTtcbiAgICB9XG4gIH1cblxuICAmLnRhYi1ibG9jayB7XG4gICAgLnRhYi1pdGVtIHtcbiAgICAgIGZsZXg6IDEgMCAwO1xuICAgICAgdGV4dC1hbGlnbjogY2VudGVyO1xuXG4gICAgICBhIHtcbiAgICAgICAgbWFyZ2luOiAwO1xuICAgICAgfVxuXG4gICAgICAuYmFkZ2Uge1xuICAgICAgICAmW2RhdGEtYmFkZ2VdOjphZnRlciB7XG4gICAgICAgICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgICAgICAgIHJpZ2h0OiAkdW5pdC1oO1xuICAgICAgICAgIHRvcDogJHVuaXQtaDtcbiAgICAgICAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgwLCAwKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gICY6bm90KC50YWItYmxvY2spIHtcbiAgICAuYmFkZ2Uge1xuICAgICAgcGFkZGluZy1yaWdodDogMDtcbiAgICB9XG4gIH1cbn1cbiIsIi8vIFRpbGVzXG4udGlsZSB7XG4gIGFsaWduLWNvbnRlbnQ6IHNwYWNlLWJldHdlZW47XG4gIGFsaWduLWl0ZW1zOiBmbGV4LXN0YXJ0O1xuICBkaXNwbGF5OiBmbGV4O1xuXG4gIC50aWxlLWljb24sXG4gIC50aWxlLWFjdGlvbiB7XG4gICAgZmxleDogMCAwIGF1dG87XG4gIH1cbiAgLnRpbGUtY29udGVudCB7XG4gICAgZmxleDogMSAxIGF1dG87XG4gICAgJjpub3QoOmZpcnN0LWNoaWxkKSB7XG4gICAgICBwYWRkaW5nLWxlZnQ6ICR1bml0LTI7XG4gICAgfVxuICAgICY6bm90KDpsYXN0LWNoaWxkKSB7XG4gICAgICBwYWRkaW5nLXJpZ2h0OiAkdW5pdC0yO1xuICAgIH1cbiAgfVxuICAudGlsZS10aXRsZSxcbiAgLnRpbGUtc3VidGl0bGUge1xuICAgIGxpbmUtaGVpZ2h0OiAkbGluZS1oZWlnaHQ7XG4gIH1cblxuICAmLnRpbGUtY2VudGVyZWQge1xuICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7XG5cbiAgICAudGlsZS1jb250ZW50IHtcbiAgICAgIG92ZXJmbG93OiBoaWRkZW47XG4gICAgfVxuXG4gICAgLnRpbGUtdGl0bGUsXG4gICAgLnRpbGUtc3VidGl0bGUge1xuICAgICAgQGluY2x1ZGUgdGV4dC1lbGxpcHNpcygpO1xuICAgICAgbWFyZ2luLWJvdHRvbTogMDtcbiAgICB9XG4gIH1cbn1cbiIsIi8vIFRvYXN0c1xuLnRvYXN0IHtcbiAgQGluY2x1ZGUgdG9hc3QtdmFyaWFudCgkZGFyay1jb2xvcik7XG4gIGJvcmRlcjogJGJvcmRlci13aWR0aCBzb2xpZCAkZGFyay1jb2xvcjtcbiAgYm9yZGVyLXJhZGl1czogJGJvcmRlci1yYWRpdXM7XG4gIGNvbG9yOiAkbGlnaHQtY29sb3I7XG4gIGRpc3BsYXk6IGJsb2NrO1xuICBwYWRkaW5nOiAkbGF5b3V0LXNwYWNpbmc7XG4gIHdpZHRoOiAxMDAlO1xuXG4gICYudG9hc3QtcHJpbWFyeSB7XG4gICAgQGluY2x1ZGUgdG9hc3QtdmFyaWFudCgkcHJpbWFyeS1jb2xvcik7XG4gIH1cblxuICAmLnRvYXN0LXN1Y2Nlc3Mge1xuICAgIEBpbmNsdWRlIHRvYXN0LXZhcmlhbnQoJHN1Y2Nlc3MtY29sb3IpO1xuICB9XG5cbiAgJi50b2FzdC13YXJuaW5nIHtcbiAgICBAaW5jbHVkZSB0b2FzdC12YXJpYW50KCR3YXJuaW5nLWNvbG9yKTtcbiAgfVxuXG4gICYudG9hc3QtZXJyb3Ige1xuICAgIEBpbmNsdWRlIHRvYXN0LXZhcmlhbnQoJGVycm9yLWNvbG9yKTtcbiAgfVxuXG4gIGEge1xuICAgIGNvbG9yOiAkbGlnaHQtY29sb3I7XG4gICAgdGV4dC1kZWNvcmF0aW9uOiB1bmRlcmxpbmU7XG4gICAgXG4gICAgJjpmb2N1cyxcbiAgICAmOmhvdmVyLFxuICAgICY6YWN0aXZlLFxuICAgICYuYWN0aXZlIHtcbiAgICAgIG9wYWNpdHk6IC43NTtcbiAgICB9XG4gIH1cblxuICAuYnRuLWNsZWFyIHtcbiAgICBtYXJnaW46ICR1bml0LWg7XG4gIH1cblxuICBwIHtcbiAgICAmOmxhc3QtY2hpbGQge1xuICAgICAgbWFyZ2luLWJvdHRvbTogMDtcbiAgICB9XG4gIH1cbn1cbiIsIi8vIFRvb2x0aXBzXG4udG9vbHRpcCB7XG4gIHBvc2l0aW9uOiByZWxhdGl2ZTtcbiAgJjo6YWZ0ZXIge1xuICAgIGJhY2tncm91bmQ6IHJnYmEoJGRhcmstY29sb3IsIC45NSk7XG4gICAgYm9yZGVyLXJhZGl1czogJGJvcmRlci1yYWRpdXM7XG4gICAgYm90dG9tOiAxMDAlO1xuICAgIGNvbG9yOiAkbGlnaHQtY29sb3I7XG4gICAgY29udGVudDogYXR0cihkYXRhLXRvb2x0aXApO1xuICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgIGZvbnQtc2l6ZTogJGZvbnQtc2l6ZS1zbTtcbiAgICBsZWZ0OiA1MCU7XG4gICAgbWF4LXdpZHRoOiAkY29udHJvbC13aWR0aC1zbTtcbiAgICBvcGFjaXR5OiAwO1xuICAgIG92ZXJmbG93OiBoaWRkZW47XG4gICAgcGFkZGluZzogJHVuaXQtMSAkdW5pdC0yO1xuICAgIHBvaW50ZXItZXZlbnRzOiBub25lO1xuICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICB0ZXh0LW92ZXJmbG93OiBlbGxpcHNpcztcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtNTAlLCAkdW5pdC0yKTtcbiAgICB0cmFuc2l0aW9uOiBvcGFjaXR5IC4ycywgdHJhbnNmb3JtIC4ycztcbiAgICB3aGl0ZS1zcGFjZTogcHJlO1xuICAgIHotaW5kZXg6ICR6aW5kZXgtMztcbiAgfVxuICAmOmZvY3VzLFxuICAmOmhvdmVyIHtcbiAgICAmOjphZnRlciB7XG4gICAgICBvcGFjaXR5OiAxO1xuICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUoLTUwJSwgLSR1bml0LTEpO1xuICAgIH1cbiAgfVxuICAmW2Rpc2FibGVkXSxcbiAgJi5kaXNhYmxlZCB7XG4gICAgcG9pbnRlci1ldmVudHM6IGF1dG87XG4gIH1cblxuICAmLnRvb2x0aXAtcmlnaHQge1xuICAgICY6OmFmdGVyIHtcbiAgICAgIGJvdHRvbTogNTAlO1xuICAgICAgbGVmdDogMTAwJTtcbiAgICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlKC0kdW5pdC0xLCA1MCUpO1xuICAgIH1cbiAgICAmOmZvY3VzLFxuICAgICY6aG92ZXIge1xuICAgICAgJjo6YWZ0ZXIge1xuICAgICAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgkdW5pdC0xLCA1MCUpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gICYudG9vbHRpcC1ib3R0b20ge1xuICAgICY6OmFmdGVyIHtcbiAgICAgIGJvdHRvbTogYXV0bztcbiAgICAgIHRvcDogMTAwJTtcbiAgICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlKC01MCUsIC0kdW5pdC0yKTtcbiAgICB9XG4gICAgJjpmb2N1cyxcbiAgICAmOmhvdmVyIHtcbiAgICAgICY6OmFmdGVyIHtcbiAgICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUoLTUwJSwgJHVuaXQtMSk7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIFxuICAmLnRvb2x0aXAtbGVmdCB7XG4gICAgJjo6YWZ0ZXIge1xuICAgICAgYm90dG9tOiA1MCU7XG4gICAgICBsZWZ0OiBhdXRvO1xuICAgICAgcmlnaHQ6IDEwMCU7XG4gICAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgkdW5pdC0yLCA1MCUpO1xuICAgIH1cbiAgICAmOmZvY3VzLFxuICAgICY6aG92ZXIge1xuICAgICAgJjo6YWZ0ZXIge1xuICAgICAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtJHVuaXQtMSwgNTAlKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cbiIsIi8vIEFuaW1hdGlvbnNcbkBrZXlmcmFtZXMgbG9hZGluZyB7XG4gIDAlIHtcbiAgICB0cmFuc2Zvcm06IHJvdGF0ZSgwZGVnKTtcbiAgfVxuICAxMDAlIHtcbiAgICB0cmFuc2Zvcm06IHJvdGF0ZSgzNjBkZWcpO1xuICB9XG59XG5cbkBrZXlmcmFtZXMgc2xpZGUtZG93biB7XG4gIDAlIHtcbiAgICBvcGFjaXR5OiAwO1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWSgtJHVuaXQtOCk7XG4gIH1cbiAgMTAwJSB7XG4gICAgb3BhY2l0eTogMTtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVkoMCk7XG4gIH1cbn1cbiIsIkBpbXBvcnQgXCJ1dGlsaXRpZXMvY29sb3JzXCI7XG5AaW1wb3J0IFwidXRpbGl0aWVzL2N1cnNvcnNcIjtcbkBpbXBvcnQgXCJ1dGlsaXRpZXMvZGlzcGxheVwiO1xuQGltcG9ydCBcInV0aWxpdGllcy9kaXZpZGVyXCI7XG5AaW1wb3J0IFwidXRpbGl0aWVzL2xvYWRpbmdcIjtcbkBpbXBvcnQgXCJ1dGlsaXRpZXMvcG9zaXRpb25cIjtcbkBpbXBvcnQgXCJ1dGlsaXRpZXMvc2hhcGVzXCI7XG5AaW1wb3J0IFwidXRpbGl0aWVzL3RleHRcIjtcbiIsIi8vIFRleHQgY29sb3JzXG5AaW5jbHVkZSB0ZXh0LWNvbG9yLXZhcmlhbnQoXCIudGV4dC1wcmltYXJ5XCIsICRwcmltYXJ5LWNvbG9yKTtcblxuQGluY2x1ZGUgdGV4dC1jb2xvci12YXJpYW50KFwiLnRleHQtc2Vjb25kYXJ5XCIsICRzZWNvbmRhcnktY29sb3ItZGFyayk7XG5cbkBpbmNsdWRlIHRleHQtY29sb3ItdmFyaWFudChcIi50ZXh0LWdyYXlcIiwgJGdyYXktY29sb3IpO1xuXG5AaW5jbHVkZSB0ZXh0LWNvbG9yLXZhcmlhbnQoXCIudGV4dC1saWdodFwiLCAkbGlnaHQtY29sb3IpO1xuXG5AaW5jbHVkZSB0ZXh0LWNvbG9yLXZhcmlhbnQoXCIudGV4dC1kYXJrXCIsICRib2R5LWZvbnQtY29sb3IpO1xuXG5AaW5jbHVkZSB0ZXh0LWNvbG9yLXZhcmlhbnQoXCIudGV4dC1zdWNjZXNzXCIsICRzdWNjZXNzLWNvbG9yKTtcblxuQGluY2x1ZGUgdGV4dC1jb2xvci12YXJpYW50KFwiLnRleHQtd2FybmluZ1wiLCAkd2FybmluZy1jb2xvcik7XG5cbkBpbmNsdWRlIHRleHQtY29sb3ItdmFyaWFudChcIi50ZXh0LWVycm9yXCIsICRlcnJvci1jb2xvcik7XG5cbi8vIEJhY2tncm91bmQgY29sb3JzXG5AaW5jbHVkZSBiZy1jb2xvci12YXJpYW50KFwiLmJnLXByaW1hcnlcIiwgJHByaW1hcnktY29sb3IpO1xuXG5AaW5jbHVkZSBiZy1jb2xvci12YXJpYW50KFwiLmJnLXNlY29uZGFyeVwiLCAkc2Vjb25kYXJ5LWNvbG9yKTtcblxuQGluY2x1ZGUgYmctY29sb3ItdmFyaWFudChcIi5iZy1kYXJrXCIsICRkYXJrLWNvbG9yKTtcblxuQGluY2x1ZGUgYmctY29sb3ItdmFyaWFudChcIi5iZy1ncmF5XCIsICRiZy1jb2xvcik7XG5cbkBpbmNsdWRlIGJnLWNvbG9yLXZhcmlhbnQoXCIuYmctc3VjY2Vzc1wiLCAkc3VjY2Vzcy1jb2xvcik7XG5cbkBpbmNsdWRlIGJnLWNvbG9yLXZhcmlhbnQoXCIuYmctd2FybmluZ1wiLCAkd2FybmluZy1jb2xvcik7XG5cbkBpbmNsdWRlIGJnLWNvbG9yLXZhcmlhbnQoXCIuYmctZXJyb3JcIiwgJGVycm9yLWNvbG9yKTtcbiIsIi8vIEN1cnNvcnNcbi5jLWhhbmQge1xuICBjdXJzb3I6IHBvaW50ZXI7XG59XG5cbi5jLW1vdmUge1xuICBjdXJzb3I6IG1vdmU7XG59XG5cbi5jLXpvb20taW4ge1xuICBjdXJzb3I6IHpvb20taW47XG59XG5cbi5jLXpvb20tb3V0IHtcbiAgY3Vyc29yOiB6b29tLW91dDtcbn1cblxuLmMtbm90LWFsbG93ZWQge1xuICBjdXJzb3I6IG5vdC1hbGxvd2VkO1xufVxuXG4uYy1hdXRvIHtcbiAgY3Vyc29yOiBhdXRvO1xufSIsIi8vIERpc3BsYXlcbi5kLWJsb2NrIHtcbiAgZGlzcGxheTogYmxvY2s7XG59XG4uZC1pbmxpbmUge1xuICBkaXNwbGF5OiBpbmxpbmU7XG59XG4uZC1pbmxpbmUtYmxvY2sge1xuICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XG59XG4uZC1mbGV4IHtcbiAgZGlzcGxheTogZmxleDtcbn1cbi5kLWlubGluZS1mbGV4IHtcbiAgZGlzcGxheTogaW5saW5lLWZsZXg7XG59XG4uZC1ub25lLFxuLmQtaGlkZSB7XG4gIGRpc3BsYXk6IG5vbmUgIWltcG9ydGFudDtcbn1cbi5kLXZpc2libGUge1xuICB2aXNpYmlsaXR5OiB2aXNpYmxlO1xufVxuLmQtaW52aXNpYmxlIHtcbiAgdmlzaWJpbGl0eTogaGlkZGVuO1xufVxuLnRleHQtaGlkZSB7XG4gIGJhY2tncm91bmQ6IHRyYW5zcGFyZW50O1xuICBib3JkZXI6IDA7XG4gIGNvbG9yOiB0cmFuc3BhcmVudDtcbiAgZm9udC1zaXplOiAwO1xuICBsaW5lLWhlaWdodDogMDtcbiAgdGV4dC1zaGFkb3c6IG5vbmU7XG59XG4udGV4dC1hc3Npc3RpdmUge1xuICBib3JkZXI6IDA7XG4gIGNsaXA6IHJlY3QoMCwwLDAsMCk7XG4gIGhlaWdodDogMXB4O1xuICBtYXJnaW46IC0xcHg7XG4gIG92ZXJmbG93OiBoaWRkZW47XG4gIHBhZGRpbmc6IDA7XG4gIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgd2lkdGg6IDFweDtcbn1cbiIsIi8vIERpdmlkZXJcbi5kaXZpZGVyLFxuLmRpdmlkZXItdmVydCB7XG4gIGRpc3BsYXk6IGJsb2NrO1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG5cbiAgJltkYXRhLWNvbnRlbnRdOjphZnRlciB7XG4gICAgYmFja2dyb3VuZDogJGJnLWNvbG9yLWxpZ2h0O1xuICAgIGNvbG9yOiAkZ3JheS1jb2xvcjtcbiAgICBjb250ZW50OiBhdHRyKGRhdGEtY29udGVudCk7XG4gICAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xuICAgIGZvbnQtc2l6ZTogJGZvbnQtc2l6ZS1zbTtcbiAgICBwYWRkaW5nOiAwICR1bml0LTI7XG4gICAgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKC0kZm9udC1zaXplLXNtICsgJGJvcmRlci13aWR0aCk7XG4gIH1cbn1cblxuLmRpdmlkZXIge1xuICBib3JkZXItdG9wOiAkYm9yZGVyLXdpZHRoIHNvbGlkICRib3JkZXItY29sb3ItbGlnaHQ7XG4gIGhlaWdodDogJGJvcmRlci13aWR0aDtcbiAgbWFyZ2luOiAkdW5pdC0yIDA7XG5cbiAgJltkYXRhLWNvbnRlbnRdIHtcbiAgICBtYXJnaW46ICR1bml0LTQgMDtcbiAgfVxufVxuXG4uZGl2aWRlci12ZXJ0IHtcbiAgZGlzcGxheTogYmxvY2s7XG4gIHBhZGRpbmc6ICR1bml0LTQ7XG5cbiAgJjo6YmVmb3JlIHtcbiAgICBib3JkZXItbGVmdDogJGJvcmRlci13aWR0aCBzb2xpZCAkYm9yZGVyLWNvbG9yO1xuICAgIGJvdHRvbTogJHVuaXQtMjtcbiAgICBjb250ZW50OiBcIlwiO1xuICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgIGxlZnQ6IDUwJTtcbiAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgdG9wOiAkdW5pdC0yO1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWCgtNTAlKTtcbiAgfVxuXG4gICZbZGF0YS1jb250ZW50XTo6YWZ0ZXIge1xuICAgIGxlZnQ6IDUwJTtcbiAgICBwYWRkaW5nOiAkdW5pdC0xIDA7XG4gICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgIHRvcDogNTAlO1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlKC01MCUsIC01MCUpO1xuICB9XG59XG4iLCIvLyBMb2FkaW5nXG4ubG9hZGluZyB7XG4gIGNvbG9yOiB0cmFuc3BhcmVudCAhaW1wb3J0YW50O1xuICBtaW4taGVpZ2h0OiAkdW5pdC00O1xuICBwb2ludGVyLWV2ZW50czogbm9uZTtcbiAgcG9zaXRpb246IHJlbGF0aXZlO1xuICAmOjphZnRlciB7XG4gICAgYW5pbWF0aW9uOiBsb2FkaW5nIDUwMG1zIGluZmluaXRlIGxpbmVhcjtcbiAgICBib3JkZXI6ICRib3JkZXItd2lkdGgtbGcgc29saWQgJHByaW1hcnktY29sb3I7XG4gICAgYm9yZGVyLXJhZGl1czogNTAlO1xuICAgIGJvcmRlci1yaWdodC1jb2xvcjogdHJhbnNwYXJlbnQ7XG4gICAgYm9yZGVyLXRvcC1jb2xvcjogdHJhbnNwYXJlbnQ7XG4gICAgY29udGVudDogXCJcIjtcbiAgICBkaXNwbGF5OiBibG9jaztcbiAgICBoZWlnaHQ6ICR1bml0LTQ7XG4gICAgbGVmdDogNTAlO1xuICAgIG1hcmdpbi1sZWZ0OiAtJHVuaXQtMjtcbiAgICBtYXJnaW4tdG9wOiAtJHVuaXQtMjtcbiAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgdG9wOiA1MCU7XG4gICAgd2lkdGg6ICR1bml0LTQ7XG4gICAgei1pbmRleDogJHppbmRleC0wO1xuICB9XG5cbiAgJi5sb2FkaW5nLWxnIHtcbiAgICBtaW4taGVpZ2h0OiAkdW5pdC0xMDtcbiAgICAmOjphZnRlciB7XG4gICAgICBoZWlnaHQ6ICR1bml0LTg7XG4gICAgICBtYXJnaW4tbGVmdDogLSR1bml0LTQ7XG4gICAgICBtYXJnaW4tdG9wOiAtJHVuaXQtNDtcbiAgICAgIHdpZHRoOiAkdW5pdC04O1xuICAgIH1cbiAgfVxufVxuIiwiLy8gUG9zaXRpb25cbi5jbGVhcmZpeCB7XG4gIEBpbmNsdWRlIGNsZWFyZml4KCk7XG59XG5cbi5mbG9hdC1sZWZ0IHtcbiAgZmxvYXQ6IGxlZnQgIWltcG9ydGFudDtcbn1cblxuLmZsb2F0LXJpZ2h0IHtcbiAgZmxvYXQ6IHJpZ2h0ICFpbXBvcnRhbnQ7XG59XG5cbi5wLXJlbGF0aXZlIHtcbiAgcG9zaXRpb246IHJlbGF0aXZlICFpbXBvcnRhbnQ7XG59XG5cbi5wLWFic29sdXRlIHtcbiAgcG9zaXRpb246IGFic29sdXRlICFpbXBvcnRhbnQ7XG59XG5cbi5wLWZpeGVkIHtcbiAgcG9zaXRpb246IGZpeGVkICFpbXBvcnRhbnQ7XG59XG5cbi5wLXN0aWNreSB7XG4gIHBvc2l0aW9uOiBzdGlja3kgIWltcG9ydGFudDtcbn1cblxuLnAtY2VudGVyZWQge1xuICBkaXNwbGF5OiBibG9jaztcbiAgZmxvYXQ6IG5vbmU7XG4gIG1hcmdpbi1sZWZ0OiBhdXRvO1xuICBtYXJnaW4tcmlnaHQ6IGF1dG87XG59XG5cbi5mbGV4LWNlbnRlcmVkIHtcbiAgYWxpZ24taXRlbXM6IGNlbnRlcjtcbiAgZGlzcGxheTogZmxleDtcbiAganVzdGlmeS1jb250ZW50OiBjZW50ZXI7XG59XG5cbi8vIFNwYWNpbmdcbkBpbmNsdWRlIG1hcmdpbi12YXJpYW50KDAsIDApO1xuXG5AaW5jbHVkZSBtYXJnaW4tdmFyaWFudCgxLCAkdW5pdC0xKTtcblxuQGluY2x1ZGUgbWFyZ2luLXZhcmlhbnQoMiwgJHVuaXQtMik7XG5cbkBpbmNsdWRlIHBhZGRpbmctdmFyaWFudCgwLCAwKTtcblxuQGluY2x1ZGUgcGFkZGluZy12YXJpYW50KDEsICR1bml0LTEpO1xuXG5AaW5jbHVkZSBwYWRkaW5nLXZhcmlhbnQoMiwgJHVuaXQtMik7XG4iLCIvLyBTaGFwZXNcbi5zLXJvdW5kZWQge1xuICBib3JkZXItcmFkaXVzOiAkYm9yZGVyLXJhZGl1cztcbn1cblxuLnMtY2lyY2xlIHtcbiAgYm9yZGVyLXJhZGl1czogNTAlO1xufSIsIi8vIFRleHRcbi8vIFRleHQgYWxpZ25tZW50IHV0aWxpdGllc1xuLnRleHQtbGVmdCB7XG4gIHRleHQtYWxpZ246IGxlZnQ7XG59XG5cbi50ZXh0LXJpZ2h0IHtcbiAgdGV4dC1hbGlnbjogcmlnaHQ7XG59XG5cbi50ZXh0LWNlbnRlciB7XG4gIHRleHQtYWxpZ246IGNlbnRlcjtcbn1cblxuLnRleHQtanVzdGlmeSB7XG4gIHRleHQtYWxpZ246IGp1c3RpZnk7XG59XG5cbi8vIFRleHQgdHJhbnNmb3JtIHV0aWxpdGllc1xuLnRleHQtbG93ZXJjYXNlIHtcbiAgdGV4dC10cmFuc2Zvcm06IGxvd2VyY2FzZTtcbn1cblxuLnRleHQtdXBwZXJjYXNlIHtcbiAgdGV4dC10cmFuc2Zvcm06IHVwcGVyY2FzZTtcbn1cblxuLnRleHQtY2FwaXRhbGl6ZSB7XG4gIHRleHQtdHJhbnNmb3JtOiBjYXBpdGFsaXplO1xufVxuXG4vLyBUZXh0IHN0eWxlIHV0aWxpdGllc1xuLnRleHQtbm9ybWFsIHtcbiAgZm9udC13ZWlnaHQ6IG5vcm1hbDtcbn1cblxuLnRleHQtYm9sZCB7XG4gIGZvbnQtd2VpZ2h0OiBib2xkO1xufVxuXG4udGV4dC1pdGFsaWMge1xuICBmb250LXN0eWxlOiBpdGFsaWM7XG59XG5cbi50ZXh0LWxhcmdlIHtcbiAgZm9udC1zaXplOiAxLjJlbTtcbn1cblxuLy8gVGV4dCBvdmVyZmxvdyB1dGlsaXRpZXNcbi50ZXh0LWVsbGlwc2lzIHtcbiAgQGluY2x1ZGUgdGV4dC1lbGxpcHNpcygpO1xufVxuXG4udGV4dC1jbGlwIHtcbiAgb3ZlcmZsb3c6IGhpZGRlbjtcbiAgdGV4dC1vdmVyZmxvdzogY2xpcDtcbiAgd2hpdGUtc3BhY2U6IG5vd3JhcDtcbn1cblxuLnRleHQtYnJlYWsge1xuICBoeXBoZW5zOiBhdXRvO1xuICB3b3JkLWJyZWFrOiBicmVhay13b3JkO1xuICB3b3JkLXdyYXA6IGJyZWFrLXdvcmQ7XG59XG4iLCIuc2VhcmNoLWlucHV0LCBbZGF0YS1ncmF2LWZpZWxkPVwiYXJyYXlcIl0gaW5wdXQsIFtkYXRhLWdyYXYtZmllbGQ9XCJhcnJheVwiXSB0ZXh0YXJlYSB7XG4gIEBleHRlbmQgLmZvcm0taW5wdXQ7XG59XG5cbi5idXR0b24ge1xuICBAZXh0ZW5kIC5idG47XG59XG4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBS0Esd0VBQThFO0FhTDlFLHdDQUF3QztBQUN4QywyRUFBMkU7QUFFM0Usb05BS0c7QUFFSCx5RkFDZ0Y7QUFFaEYsQUFBQSxJQUFJLENBQUMsRUFDSCxXQUFXLEVBQUUsVUFBVSxFQUFFLE9BQU8sQ0FDaEMsb0JBQW9CLEVBQUUsSUFBSSxFQUFFLE9BQU8sQ0FDbkMsd0JBQXdCLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFDeEM7O0FBRUQseUZBQ2dGO0FBRWhGLHVEQUVHO0FBRUgsQUFBQSxJQUFJLENBQUMsRUFDSCxNQUFNLEVBQUUsQ0FBQyxHQUNWOztBQUVELHdDQUVHO0FBRUgsQUFBQSxPQUFPLEVBQ1AsS0FBSyxFQUNMLE1BQU0sRUFDTixNQUFNLEVBQ04sR0FBRyxFQUNILE9BQU8sQ0FBQyxFQUNOLE9BQU8sRUFBRSxLQUFLLEdBQ2Y7O0FBRUQsZ0lBR0c7QUFFSCxBQUFBLEVBQUUsQ0FBQyxFQUNELFNBQVMsRUFBRSxHQUFHLEVBQ2QsTUFBTSxFQUFFLFFBQVEsR0FDakI7O0FBRUQsaUdBQ2dGO0FBRWhGLDBFQUdHO0FBRUgsQUFBQSxVQUFVLEVBQ1YsTUFBTSxFQUNOLElBQUksQ0FBQyxFQUFFLE9BQU8sQ0FDWixPQUFPLEVBQUUsS0FBSyxHQUNmOztBQUVELGdEQUVHO0FBRUgscUZBR0c7QUFFSCxBQUFBLEVBQUUsQ0FBQyxFQUNELFVBQVUsRUFBRSxXQUFXLEVBQUUsT0FBTyxDQUNoQyxNQUFNLEVBQUUsQ0FBQyxFQUFFLE9BQU8sQ0FDbEIsUUFBUSxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQzNCOztBQUVELDBJQUdHO0FBRUgscUdBQ2dGO0FBRWhGLHlIQUdHO0FBRUgsQUFBQSxDQUFDLENBQUMsRUFDQSxnQkFBZ0IsRUFBRSxXQUFXLEVBQUUsT0FBTyxDQUN0Qyw0QkFBNEIsRUFBRSxPQUFPLEVBQUUsT0FBTyxFQUMvQzs7QUFFRCw4R0FHRztBQUVILEFBQUEsQ0FBQyxBQUFBLE9BQU8sRUFDUixDQUFDLEFBQUEsTUFBTSxDQUFDLEVBQ04sYUFBYSxFQUFFLENBQUMsR0FDakI7O0FBRUQseUNBRUc7QUFFSCxBQUFBLE9BQU8sQ0FBQyxFQUNOLFVBQVUsRUFBRSxNQUFNLEdBQ25COztBQUVELHVJQUdHO0FBRUgsa0ZBRUc7QUFFSCxBQUFBLENBQUMsRUFDRCxNQUFNLENBQUMsRUFDTCxXQUFXLEVBQUUsT0FBTyxHQUNyQjs7QUFFRCwrREFFRztBQVBILEFBQUEsQ0FBQyxFQUNELE1BQU0sQ0FTQyxFQUNMLFdBQVcsRUFBRSxNQUFNLEdBQ3BCOztBQUVELGdJQUdHO0FBRUgsQUFBQSxJQUFJLEVBQ0osR0FBRyxFQUNILEdBQUcsRUFDSCxJQUFJLENBQUMsRUFDSCxXQUFXLEVYekdNLFNBQVMsRUFBRSxlQUFlLEVBQUUsYUFBYSxFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsU0FBUyxFV3lHckQsaUJBQWlCLENBQ2pELFNBQVMsRUFBRSxHQUFHLEVBQUUsT0FBTyxFQUN4Qjs7QUFFRCxrREFFRztBQUVILEFBQUEsR0FBRyxDQUFDLEVBQ0YsVUFBVSxFQUFFLE1BQU0sR0FDbkI7O0FBRUQsK0RBRUc7QUFFSCxpREFFRztBQUVILEFBQUEsS0FBSyxDQUFDLEVBQ0osU0FBUyxFQUFFLEdBQUcsRUFDZCxXQUFXLEVBQUUsR0FBRyxFQUFFLGFBQWEsRUFDaEM7O0FBRUQsdUZBR0c7QUFFSCxBQUFBLEdBQUcsRUFDSCxHQUFHLENBQUMsRUFDRixTQUFTLEVBQUUsR0FBRyxFQUNkLFdBQVcsRUFBRSxDQUFDLEVBQ2QsUUFBUSxFQUFFLFFBQVEsRUFDbEIsY0FBYyxFQUFFLFFBQVEsR0FDekI7O0FBRUQsQUFBQSxHQUFHLENBQUMsRUFDRixNQUFNLEVBQUUsT0FBTyxHQUNoQjs7QUFFRCxBQUFBLEdBQUcsQ0FBQyxFQUNGLEdBQUcsRUFBRSxNQUFNLEdBQ1o7O0FBRUQsaUdBQ2dGO0FBRWhGLHdDQUVHO0FBRUgsQUFBQSxLQUFLLEVBQ0wsS0FBSyxDQUFDLEVBQ0osT0FBTyxFQUFFLFlBQVksR0FDdEI7O0FBRUQsMENBRUc7QUFFSCxBQUFBLEtBQUssQUFBQSxJQUFLLEVBQUEsQUFBQSxRQUFDLEFBQUEsR0FBVyxFQUNwQixPQUFPLEVBQUUsSUFBSSxFQUNiLE1BQU0sRUFBRSxDQUFDLEdBQ1Y7O0FBRUQsMERBRUc7QUFFSCxBQUFBLEdBQUcsQ0FBQyxFQUNGLFlBQVksRUFBRSxJQUFJLEdBQ25COztBQUVELCtCQUVHO0FBRUgsQUFBQSxHQUFHLEFBQUEsSUFBSyxDQUFBLEtBQUssRUFBRSxFQUNiLFFBQVEsRUFBRSxNQUFNLEdBQ2pCOztBQUVELHNGQUNnRjtBQUVoRiwyR0FHRztBQUVILEFBQUEsTUFBTSxFQUNOLEtBQUssRUFDTCxRQUFRLEVBQ1IsTUFBTSxFQUNOLFFBQVEsQ0FBQyxFQUNQLFdBQVcsRUFBRSxPQUFPLEVBQUUsaUJBQWlCLENBQ3ZDLFNBQVMsRUFBRSxPQUFPLEVBQUUsaUJBQWlCLENBQ3JDLFdBQVcsRUFBRSxPQUFPLEVBQUUsaUJBQWlCLENBQ3ZDLE1BQU0sRUFBRSxDQUFDLEVBQUUsT0FBTyxFQUNuQjs7QUFFRCw2REFHRztBQUVILEFBQUEsTUFBTSxFQUNOLEtBQUssQ0FBQyxFQUFFLE9BQU8sQ0FDYixRQUFRLEVBQUUsT0FBTyxHQUNsQjs7QUFFRCxpSUFHRztBQUVILEFBQUEsTUFBTSxFQUNOLE1BQU0sQ0FBQyxFQUFFLE9BQU8sQ0FDZCxjQUFjLEVBQUUsSUFBSSxHQUNyQjs7QUFFRCx3S0FJRztBQUVILEFBQUEsTUFBTSxFQUNOLElBQUksRUFBQyxBQUFBLElBQUMsQ0FBSyxRQUFRLEFBQWIsSUFDTixBQUFBLElBQUMsQ0FBSyxPQUFPLEFBQVosSUFDRCxBQUFBLElBQUMsQ0FBSyxRQUFRLEFBQWIsRUFBZSxFQUNkLGtCQUFrQixFQUFFLE1BQU0sRUFBRSxPQUFPLEVBQ3BDOztBQUVELHNEQUVHO0FBRUgsQUFBQSxNQUFNLEFBQUEsa0JBQWtCLEdBQ3hCLEFBQUEsSUFBQyxDQUFLLFFBQVEsQUFBYixDQUFjLGtCQUFrQixHQUNqQyxBQUFBLElBQUMsQ0FBSyxPQUFPLEFBQVosQ0FBYSxrQkFBa0IsR0FDaEMsQUFBQSxJQUFDLENBQUssUUFBUSxBQUFiLENBQWMsa0JBQWtCLENBQUMsRUFDaEMsWUFBWSxFQUFFLElBQUksRUFDbEIsT0FBTyxFQUFFLENBQUMsR0FDWDs7QUFFRCxxRUFFRztBQUdILHNGQUVHO0FBRUgsQUFBQSxRQUFRLENBQUMsRUFDUCxNQUFNLEVBQUUsQ0FBQyxFQUNULE1BQU0sRUFBRSxDQUFDLEVBQ1QsT0FBTyxFQUFFLENBQUMsR0FDWDs7QUFFRCxtT0FLRztBQUVILEFBQUEsTUFBTSxDQUFDLEVBQ0wsVUFBVSxFQUFFLFVBQVUsRUFBRSxPQUFPLENBQy9CLEtBQUssRUFBRSxPQUFPLEVBQUUsT0FBTyxDQUN2QixPQUFPLEVBQUUsS0FBSyxFQUFFLE9BQU8sQ0FDdkIsU0FBUyxFQUFFLElBQUksRUFBRSxPQUFPLENBQ3hCLE9BQU8sRUFBRSxDQUFDLEVBQUUsT0FBTyxDQUNuQixXQUFXLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFDN0I7O0FBRUQsZ0hBR0c7QUFFSCxBQUFBLFFBQVEsQ0FBQyxFQUNQLE9BQU8sRUFBRSxZQUFZLEVBQUUsT0FBTyxDQUM5QixjQUFjLEVBQUUsUUFBUSxFQUFFLE9BQU8sRUFDbEM7O0FBRUQsbURBRUc7QUFFSCxBQUFBLFFBQVEsQ0FBQyxFQUNQLFFBQVEsRUFBRSxJQUFJLEdBQ2Y7O0FBRUQsZ0ZBR0c7Q0FFSCxBQUFBLEFBQUEsSUFBQyxDQUFLLFVBQVUsQUFBZixJQUNELEFBQUEsSUFBQyxDQUFLLE9BQU8sQUFBWixFQUFjLEVBQ2IsVUFBVSxFQUFFLFVBQVUsRUFBRSxPQUFPLENBQy9CLE9BQU8sRUFBRSxDQUFDLEVBQUUsT0FBTyxFQUNwQjs7QUFFRCw2RUFFRztDQUVILEFBQUEsQUFBQSxJQUFDLENBQUssUUFBUSxBQUFiLENBQWMsMkJBQTJCLEdBQzFDLEFBQUEsSUFBQyxDQUFLLFFBQVEsQUFBYixDQUFjLDJCQUEyQixDQUFDLEVBQ3pDLE1BQU0sRUFBRSxJQUFJLEdBQ2I7O0FBRUQsa0dBR0c7Q0FFSCxBQUFBLEFBQUEsSUFBQyxDQUFLLFFBQVEsQUFBYixFQUFlLEVBQ2Qsa0JBQWtCLEVBQUUsU0FBUyxFQUFFLE9BQU8sQ0FDdEMsY0FBYyxFQUFFLElBQUksRUFBRSxPQUFPLEVBQzlCOztBQUVELGlGQUVHO0NBRUgsQUFBQSxBQUFBLElBQUMsQ0FBSyxRQUFRLEFBQWIsQ0FBYyw4QkFBOEIsR0FDN0MsQUFBQSxJQUFDLENBQUssUUFBUSxBQUFiLENBQWMsMkJBQTJCLENBQUMsRUFDekMsa0JBQWtCLEVBQUUsSUFBSSxHQUN6Qjs7QUFFRCw2SEFHRztBQUVILEFBQUEsNEJBQTRCLENBQUMsRUFDM0Isa0JBQWtCLEVBQUUsTUFBTSxFQUFFLE9BQU8sQ0FDbkMsSUFBSSxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQ3ZCOztBQUVELDRGQUNnRjtBQUVoRiw0RkFHRztBQUVILEFBQUEsT0FBTyxFQUNQLElBQUksQ0FBQyxFQUNILE9BQU8sRUFBRSxLQUFLLEdBQ2Y7O0FBRUQsOENBRUc7QUFFSCxBQUFBLE9BQU8sQ0FBQyxFQUNOLE9BQU8sRUFBRSxTQUFTLEVBQ2xCLE9BQU8sRUFBRSxJQUFJLEdBQ2Q7O0FBRUQsMEZBQ2dGO0FBRWhGLHdDQUVHO0FBRUgsQUFBQSxNQUFNLENBQUMsRUFDTCxPQUFPLEVBQUUsWUFBWSxHQUN0Qjs7QUFFRCxxQ0FFRztBQUVILEFBQUEsUUFBUSxDQUFDLEVBQ1AsT0FBTyxFQUFFLElBQUksR0FDZDs7QUFFRCx1RkFDZ0Y7QUFFaEYseUNBRUc7Q0FFSCxBQUFBLEFBQUEsTUFBQyxBQUFBLEVBQVEsRUFDUCxPQUFPLEVBQUUsSUFBSSxHQUNkOztBQzViRCxBQUFBLENBQUMsRUFDRCxDQUFDLEFBQUEsUUFBUSxFQUNULENBQUMsQUFBQSxPQUFPLENBQUMsRUFDUCxVQUFVLEVBQUUsT0FBTyxHQUNwQjs7QURRRCxBQUFBLElBQUksQ0NOQyxFQUNILFVBQVUsRUFBRSxVQUFVLEVBQ3RCLFNBQVMsRVo2RE0sSUFBSSxFWTVEbkIsV0FBVyxFWjZETSxHQUFHLEVZNURwQiwyQkFBMkIsRUFBRSxXQUFXLEdBQ3pDOztBRGNELEFBQUEsSUFBSSxDQ1pDLEVBQ0gsVUFBVSxFYlpFLElBQUksRWFhaEIsS0FBSyxFYlJXLE9BQXdCLEVhU3hDLFdBQVcsRVoyQk0sYUFBYSxFQUFFLFNBQVMsRUFBRSxrQkFBa0IsRUFBRSxVQUFVLEVBQUUsTUFBTSxFQUU1RCxnQkFBZ0IsRUFBRSxVQUFVLEVZNUJqRCxTQUFTLEVac0RDLE1BQUssRVlyRGYsVUFBVSxFQUFFLE1BQU0sRUFDbEIsY0FBYyxFQUFFLGtCQUFrQixHQUNuQzs7QUQwRUQsQUFBQSxDQUFDLENDeEVDLEVBQ0EsS0FBSyxFYnZCUyxPQUFPLEVhd0JyQixPQUFPLEVBQUUsSUFBSSxFQUNiLGVBQWUsRUFBRSxJQUFJLEdBaUJ0Qjs7QUFwQkQsQUFLRSxDQUxELEFBS0UsTUFBTSxDQUFDLEVKMUJSLFVBQVUsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFLLENURFQsdUJBQU8sR2E2QnBCOztBQVBILEFBU0UsQ0FURCxBQVNFLE1BQU0sRUFUVCxDQUFDLEFBVUUsTUFBTSxFQVZULENBQUMsQUFXRSxPQUFPLEVBWFYsQ0FBQyxBQVlFLE9BQU8sQ0FBQyxFQUNQLEtBQUssRVpHUyxPQUF3QixFWUZ0QyxlQUFlLEVBQUUsU0FBUyxHQUMzQjs7QUFmSCxBQWlCRSxDQWpCRCxBQWlCRSxRQUFRLENBQUMsRUFDUixLQUFLLEVaRFUsT0FBeUIsR1lFekM7O0FDeENILEFBQUEsRUFBRSxFQUNGLEVBQUUsRUFDRixFQUFFLEVBQ0YsRUFBRSxFQUNGLEVBQUUsRUFDRixFQUFFLENBQUMsRUFDRCxLQUFLLEVBQUUsT0FBTyxFQUNkLFdBQVcsRUFBRSxHQUFHLEVBQ2hCLFdBQVcsRUFBRSxHQUFHLEVBQ2hCLGFBQWEsRUFBRSxJQUFJLEVBQ25CLFVBQVUsRUFBRSxDQUFDLEdBQ2Q7O0FBQ0QsQUFBQSxHQUFHLEVBQ0gsR0FBRyxFQUNILEdBQUcsRUFDSCxHQUFHLEVBQ0gsR0FBRyxFQUNILEdBQUcsQ0FBQyxFQUNGLFdBQVcsRUFBRSxHQUFHLEdBQ2pCOztBQUNELEFBQUEsRUFBRSxFQUNGLEdBQUcsQ0FBQyxFQUNGLFNBQVMsRUFBRSxJQUFJLEdBQ2hCOztBQUNELEFBQUEsRUFBRSxFQUNGLEdBQUcsQ0FBQyxFQUNGLFNBQVMsRUFBRSxNQUFNLEdBQ2xCOztBQUNELEFBQUEsRUFBRSxFQUNGLEdBQUcsQ0FBQyxFQUNGLFNBQVMsRUFBRSxNQUFNLEdBQ2xCOztBQUNELEFBQUEsRUFBRSxFQUNGLEdBQUcsQ0FBQyxFQUNGLFNBQVMsRUFBRSxNQUFNLEdBQ2xCOztBQUNELEFBQUEsRUFBRSxFQUNGLEdBQUcsQ0FBQyxFQUNGLFNBQVMsRUFBRSxJQUFJLEdBQ2hCOztBQUNELEFBQUEsRUFBRSxFQUNGLEdBQUcsQ0FBQyxFQUNGLFNBQVMsRUFBRSxLQUFLLEdBQ2pCOztBQUdELEFBQUEsQ0FBQyxDQUFDLEVBQ0EsTUFBTSxFQUFFLENBQUMsQ0FBQyxDQUFDLENiMEJDLE1BQU0sR2F6Qm5COztBQUdELEFBQUEsQ0FBQyxFQUNELEdBQUcsRUFDSCxDQUFDLENBQUMsRUFDQSxvQkFBb0IsRUFBRSxTQUFTLEdBQ2hDOztBQUVELEFBQUEsSUFBSSxDQUFBLEFBQUEsS0FBQyxBQUFBLEVBQU8sRUFDVixhQUFhLEViTk4sT0FBTSxDYU1nQixNQUFNLEVBQ25DLE1BQU0sRUFBRSxJQUFJLEVBQ1osZUFBZSxFQUFFLElBQUksR0FDdEI7O0FBRUQsQUFBQSxHQUFHLENBQUMsRVAvREYsYUFBYSxFTnFETixNQUFLLEVNcERaLFdBQVcsRUFBRSxJQUFJLEVBQ2pCLE9BQU8sRUFBRSxXQUFXLEVBSXBCLFVBQVUsRVBOQyxPQUFPLEVPT2xCLEtBQUssRVBOTyxJQUFJLEVjaUVoQixTQUFTLEViS0ksTUFBSyxHYUpuQjs7QUFFRCxBQUFBLElBQUksQ0FBQyxFUC9ESCxVQUFVLEVOMkJNLE9BQU8sRU0xQnZCLEtBQUssRVBEVyxPQUF3QixFY2lFeEMsYUFBYSxFYm5CTixPQUFNLENhbUJVLEtBQUssQ0FBQyxPQUE2QixFQUMxRCxhQUFhLEVibkJOLE1BQUssRWFvQlosT0FBTyxFYnJCQSxPQUFNLENBQ04sTUFBSyxDYW9CYSxDQUFDLEdBQzNCOztBQUdELEFBQUEsVUFBVSxDQUFDLEVBQ1QsV0FBVyxFYnpCSixNQUFLLENheUJrQixLQUFLLENkMUV0QixPQUF5QixFYzJFdEMsV0FBVyxFQUFFLENBQUMsRUFDZCxPQUFPLEViekJBLE1BQUssQ0FFTCxNQUFLLEdhNEJiOztBQVJELEFBS0UsVUFMUSxDQUtSLENBQUMsQUFBQSxXQUFXLENBQUMsRUFDWCxhQUFhLEVBQUUsQ0FBQyxHQUNqQjs7QUFJSCxBQUFBLEVBQUUsRUFDRixFQUFFLENBQUMsRUFDRCxNQUFNLEViakNDLE1BQUssQ2FpQ0ksQ0FBQyxDYmpDVixNQUFLLENBQUwsTUFBSyxFYWtDWixPQUFPLEVBQUUsQ0FBQyxHQVVYOztBQWJELEFBS0UsRUFMQSxDQUtBLEVBQUUsRUFMSixFQUFFLENBTUEsRUFBRSxFQUxKLEVBQUUsQ0FJQSxFQUFFLEVBSkosRUFBRSxDQUtBLEVBQUUsQ0FBQyxFQUNELE1BQU0sRWJ0Q0QsTUFBSyxDYXNDTSxDQUFDLENidENaLE1BQUssQ0FBTCxNQUFLLEdhdUNYOztBQVJILEFBVUUsRUFWQSxDQVVBLEVBQUUsRUFUSixFQUFFLENBU0EsRUFBRSxDQUFDLEVBQ0QsVUFBVSxFYjVDTCxNQUFLLEdhNkNYOztBQUdILEFBQUEsRUFBRSxDQUFDLEVBQ0QsVUFBVSxFQUFFLFdBQVcsR0FLeEI7O0FBTkQsQUFHRSxFQUhBLENBR0EsRUFBRSxDQUFDLEVBQ0QsZUFBZSxFQUFFLE1BQU0sR0FDeEI7O0FBR0gsQUFBQSxFQUFFLENBQUMsRUFDRCxVQUFVLEVBQUUsY0FBYyxHQUszQjs7QUFORCxBQUdFLEVBSEEsQ0FHQSxFQUFFLENBQUMsRUFDRCxlQUFlLEVBQUUsV0FBVyxHQUM3Qjs7QUFHSCxBQUNFLEVBREEsQ0FDQSxFQUFFLENBQUMsRUFDRCxXQUFXLEVBQUUsSUFBSSxHQUNsQjs7QUFISCxBQUlFLEVBSkEsQ0FJQSxFQUFFLENBQUMsRUFDRCxNQUFNLEVickVELE1BQUssQ2FxRU0sQ0FBQyxDYm5FWixNQUFLLENhbUVnQixDQUFDLEdBQzVCOztBQzlISCxBQUFBLElBQUksQUFBQSxLQUFNLENBQUEsRUFBRSxHQUNaLElBQUksQUFBQSxLQUFNLENBQUEsT0FBTyxHQUNqQixRQUFRLEVBQ1IsYUFBYSxDQUFDLEVBQ1osV0FBVyxFZHVDTSxhQUFhLEVBQUUsU0FBUyxFQUFFLGtCQUFrQixFQUFFLFVBQVUsRUFBRSxNQUFNLEVBR3RDLGFBQWEsRUFBRSxrQkFBa0IsRUFBRSxpQkFBaUIsRUFEMUUsZ0JBQWdCLEVBQUUsVUFBVSxHY3hDbEQ7O0FBRUQsQUFBQSxJQUFJLEFBQUEsS0FBTSxDQUFBLE9BQU8sR0FDakIsYUFBYSxDQUFDLEVBQ1osV0FBVyxFZGtDTSxhQUFhLEVBQUUsU0FBUyxFQUFFLGtCQUFrQixFQUFFLFVBQVUsRUFBRSxNQUFNLEVBSXRDLGFBQWEsRUFBRSxtQkFBbUIsRUFBRSxvQkFBb0IsRUFGOUUsZ0JBQWdCLEVBQUUsVUFBVSxHY25DbEQ7O0FBRUQsQUFBQSxJQUFJLEFBQUEsS0FBTSxDQUFBLEVBQUUsR0FDWixRQUFRLENBQUMsRUFDUCxXQUFXLEVkNkJNLGFBQWEsRUFBRSxTQUFTLEVBQUUsa0JBQWtCLEVBQUUsVUFBVSxFQUFFLE1BQU0sRUFLM0MsZUFBZSxFQUFFLDBCQUEwQixFQUFFLFdBQVcsRUFBRSxRQUFRLEVBQUUsTUFBTSxFQUgzRixnQkFBZ0IsRUFBRSxVQUFVLEdjOUJsRDs7QUFFRCxBQUFBLElBQUksQUFBQSxLQUFNLENBQUEsRUFBRSxHQUNaLFFBQVEsQ0FBQyxFQUNQLFdBQVcsRWR3Qk0sYUFBYSxFQUFFLFNBQVMsRUFBRSxrQkFBa0IsRUFBRSxVQUFVLEVBQUUsTUFBTSxFQU0zQyxlQUFlLEVBSmhDLGdCQUFnQixFQUFFLFVBQVUsR2N6QmxEOztBQUVELEFBR0UsS0FISSxDQXRCSSxFQUFFLEVBeUJWLEdBQUcsRUFITCxLQUFNLENBdEJJLEVBQUUsRUEwQlYsQ0FBQyxFQUhILEtBQU0sQ0FYSSxFQUFFLEVBYVYsR0FBRyxFQUZMLEtBQU0sQ0FYSSxFQUFFLEVBY1YsQ0FBQyxFQUZILFNBQVMsQ0FDUCxHQUFHLEVBREwsU0FBUyxDQUVQLENBQUMsQ0FBQyxFQUNBLGFBQWEsRWQwQlIsT0FBTSxDYzFCa0IsS0FBSyxFQUNsQyxlQUFlLEVBQUUsSUFBSSxHQUN0Qjs7QUFQSCxBQVNFLEtBVEksQ0F0QkksRUFBRSxFQStCVixHQUFHLEdBQUcsR0FBRyxFQVRYLEtBQU0sQ0F0QkksRUFBRSxFQWdDVixHQUFHLEdBQUcsQ0FBQyxFQVZULEtBQU0sQ0F0QkksRUFBRSxFQWlDVixHQUFHLEdBQUcsR0FBRyxFQVhYLEtBQU0sQ0F0QkksRUFBRSxFQWtDVixHQUFHLEdBQUcsQ0FBQyxFQVpULEtBQU0sQ0F0QkksRUFBRSxFQW1DVixDQUFDLEdBQUcsR0FBRyxFQWJULEtBQU0sQ0F0QkksRUFBRSxFQW9DVixDQUFDLEdBQUcsQ0FBQyxFQWRQLEtBQU0sQ0F0QkksRUFBRSxFQXFDVixDQUFDLEdBQUcsR0FBRyxFQWZULEtBQU0sQ0F0QkksRUFBRSxFQXNDVixDQUFDLEdBQUcsQ0FBQyxFQWZQLEtBQU0sQ0FYSSxFQUFFLEVBbUJWLEdBQUcsR0FBRyxHQUFHLEVBUlgsS0FBTSxDQVhJLEVBQUUsRUFvQlYsR0FBRyxHQUFHLENBQUMsRUFUVCxLQUFNLENBWEksRUFBRSxFQXFCVixHQUFHLEdBQUcsR0FBRyxFQVZYLEtBQU0sQ0FYSSxFQUFFLEVBc0JWLEdBQUcsR0FBRyxDQUFDLEVBWFQsS0FBTSxDQVhJLEVBQUUsRUF1QlYsQ0FBQyxHQUFHLEdBQUcsRUFaVCxLQUFNLENBWEksRUFBRSxFQXdCVixDQUFDLEdBQUcsQ0FBQyxFQWJQLEtBQU0sQ0FYSSxFQUFFLEVBeUJWLENBQUMsR0FBRyxHQUFHLEVBZFQsS0FBTSxDQVhJLEVBQUUsRUEwQlYsQ0FBQyxHQUFHLENBQUMsRUFkUCxTQUFTLENBT1AsR0FBRyxHQUFHLEdBQUcsRUFQWCxTQUFTLENBUVAsR0FBRyxHQUFHLENBQUMsRUFSVCxTQUFTLENBU1AsR0FBRyxHQUFHLEdBQUcsRUFUWCxTQUFTLENBVVAsR0FBRyxHQUFHLENBQUMsRUFWVCxTQUFTLENBV1AsQ0FBQyxHQUFHLEdBQUcsRUFYVCxTQUFTLENBWVAsQ0FBQyxHQUFHLENBQUMsRUFaUCxTQUFTLENBYVAsQ0FBQyxHQUFHLEdBQUcsRUFiVCxTQUFTLENBY1AsQ0FBQyxHQUFHLENBQUMsQ0FBQyxFQUNKLFdBQVcsRUFBRSxNQUFNLEdBQ3BCOztBQ3hDSCxBQUFBLE1BQU0sQ0FBQyxFQUNMLGVBQWUsRUFBRSxRQUFRLEVBQ3pCLGNBQWMsRUFBRSxDQUFDLEVBQ2pCLEtBQUssRUFBRSxJQUFJLEVBSVQsVUFBVSxFQUFFLElBQUksR0FnRG5COztBQXZERCxBQVlNLE1BWkEsQUFVSCxjQUFjLENBQ2IsS0FBSyxDQUNILEVBQUUsQUFBQSxZQUFhLENBQUEsR0FBRyxFQUFFLEVBQ2xCLFVBQVUsRWhCUFAsT0FBeUIsR2dCUTdCOztBQWRQLEFBc0JRLE1BdEJGLENBb0JGLEtBQUssQ0FDSCxFQUFFLEFBQ0MsT0FBTyxFQXRCaEIsTUFBTSxBQW1CSCxjQUFjLENBQ2IsS0FBSyxDQUNILEVBQUUsQUFDQyxPQUFPLENBQUMsRUFDUCxVQUFVLEVmQ0osT0FBcUIsR2VBNUI7O0FBeEJULEFBZ0NRLE1BaENGLEFBNkJILFlBQVksQ0FDWCxLQUFLLENBQ0gsRUFBRSxBQUNDLE1BQU0sQ0FBQyxFQUNOLFVBQVUsRWZUSixPQUFxQixHZVU1Qjs7QUFsQ1QsQUF3Q0UsTUF4Q0ksQUF3Q0gsYUFBYSxDQUFDLEVBQ2IsT0FBTyxFQUFFLEtBQUssRUFDZCxVQUFVLEVBQUUsSUFBSSxFQUNoQixjQUFjLEVBQUUsTUFBTSxFQUN0QixXQUFXLEVBQUUsTUFBTSxHQUNwQjs7QUE3Q0gsQUErQ0UsTUEvQ0ksQ0ErQ0osRUFBRSxFQS9DSixNQUFNLENBZ0RKLEVBQUUsQ0FBQyxFQUNELGFBQWEsRWZJUixPQUFNLENlSmtCLEtBQUssQ2hCNUN2QixPQUF5QixFZ0I2Q3BDLE9BQU8sRWZPRixNQUFLLENBREwsTUFBSyxHZUxYOztBQW5ESCxBQW9ERSxNQXBESSxDQW9ESixFQUFFLENBQUMsRUFDRCxtQkFBbUIsRWZDZCxNQUFLLEdlQVg7O0FDdERILEFBQUEsSUFBSSxFc0NHSixPQUFPLEN0Q0hGLEVBQ0gsVUFBVSxFQUFFLElBQUksRUFDaEIsVUFBVSxFakJBRSxJQUFJLEVpQkNoQixNQUFNLEVoQmtEQyxPQUFNLENnQmxEUyxLQUFLLENqQkhiLE9BQU8sRWlCSXJCLGFBQWEsRWhCa0ROLE1BQUssRWdCakRaLEtBQUssRWpCTFMsT0FBTyxFaUJNckIsTUFBTSxFQUFFLE9BQU8sRUFDZixPQUFPLEVBQUUsWUFBWSxFQUNyQixTQUFTLEVoQitEQyxNQUFLLEVnQjlEZixNQUFNLEVoQnNEQyxNQUFNLEVnQnJEYixXQUFXLEVoQmdFQyxNQUFNLEVnQi9EbEIsT0FBTyxFQUFFLElBQUksRUFDYixPQUFPLEVoQjZFVyxPQUFrRCxDQWpDN0QsTUFBSyxFZ0IzQ1osVUFBVSxFQUFFLE1BQU0sRUFDbEIsZUFBZSxFQUFFLElBQUksRUFDckIsVUFBVSxFQUFFLHFEQUFxRCxFQUNqRSxXQUFXLEVBQUUsSUFBSSxFQUNqQixjQUFjLEVBQUUsTUFBTSxFQUN0QixXQUFXLEVBQUUsTUFBTSxHQXdJcEI7O0FBMUpELEFBbUJFLElBbkJFLEFBbUJELE1BQU0sRXNDaEJULE9BQU8sQXRDZ0JKLE1BQU0sQ0FBQyxFUmxCUixVQUFVLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBSyxDVERULHVCQUFPLEdpQnFCcEI7O0FBckJILEFBc0JFLElBdEJFLEFBbUJELE1BQU0sRXNDaEJULE9BQU8sQXRDZ0JKLE1BQU0sRUFuQlQsSUFBSSxBQXVCRCxNQUFNLEVzQ3BCVCxPQUFPLEF0Q29CSixNQUFNLENBQUMsRUFDTixVQUFVLEVoQmZJLE9BQThCLEVnQmdCNUMsWUFBWSxFaEJsQkssT0FBMEIsRWdCbUIzQyxlQUFlLEVBQUUsSUFBSSxHQUN0Qjs7QUEzQkgsQUE0QkUsSUE1QkUsQUE0QkQsT0FBTyxFc0N6QlYsT0FBTyxBdEN5QkosT0FBTyxFQTVCVixJQUFJLEFBNkJELE9BQU8sRUFBUCxPQUFPLEFzQzFCVixPQUFPLEN0QzBCSSxFQUNQLFVBQVUsRWhCdkJPLE9BQTBCLEVnQndCM0MsWUFBWSxFQUFFLE9BQStCLEVBQzdDLEtBQUssRWpCOUJLLElBQUksRWlCK0JkLGVBQWUsRUFBRSxJQUFJLEdBT3RCOztBQXhDSCxBQW1DTSxJQW5DRixBQTRCRCxPQUFPLEFBTUwsUUFBUSxBQUNOLE9BQU8sRXNDaENkLE9BQU8sQXRDeUJKLE9BQU8sQUFNTCxRQUFRLEFBQ04sT0FBTyxFQW5DZCxJQUFJLEFBNkJELE9BQU8sQUFLTCxRQUFRLEFBQ04sT0FBTyxFQU5YLE9BQU8sQUFLTCxRQUFRLEFzQy9CYixPQUFPLEF0Q2dDQSxPQUFPLENBQUMsRUFDUCxtQkFBbUIsRWpCbENiLElBQUksRWlCbUNWLGlCQUFpQixFakJuQ1gsSUFBSSxHaUJvQ1g7O0FBdENQLEFBeUNFLElBekNFLENBeUNELEFBQUEsUUFBQyxBQUFBLEdzQ3RDSixPQUFPLEN0Q3NDSixBQUFBLFFBQUMsQUFBQSxHQXpDSixJQUFJLEFBMENELFNBQVMsRXNDdkNaLE9BQU8sQXRDdUNKLFNBQVMsRUExQ1osSUFBSSxBQTJDRCxTQUFTLEVBQVQsU0FBUyxBc0N4Q1osT0FBTyxDdEN3Q00sRUFDVCxNQUFNLEVBQUUsT0FBTyxFQUNmLE9BQU8sRUFBRSxFQUFFLEVBQ1gsY0FBYyxFQUFFLElBQUksR0FDckI7O0FBL0NILEFBa0RFLElBbERFLEFBa0RELFlBQVksRUFBWixZQUFZLEFzQy9DZixPQUFPLEN0QytDUyxFQUNaLFVBQVUsRWpCbkRFLE9BQU8sRWlCb0RuQixZQUFZLEVoQjdDSyxPQUEwQixFZ0I4QzNDLEtBQUssRWpCbkRLLElBQUksR2lCc0VmOztBQXhFSCxBQXNESSxJQXREQSxBQWtERCxZQUFZLEFBSVYsTUFBTSxFQUpSLFlBQVksQXNDL0NmLE9BQU8sQXRDbURGLE1BQU0sRUF0RFgsSUFBSSxBQWtERCxZQUFZLEFBS1YsTUFBTSxFQUxSLFlBQVksQXNDL0NmLE9BQU8sQXRDb0RGLE1BQU0sQ0FBQyxFQUNOLFVBQVUsRUFBRSxPQUErQixFQUMzQyxZQUFZLEVBQUUsT0FBK0IsRUFDN0MsS0FBSyxFakJ4REcsSUFBSSxHaUJ5RGI7O0FBM0RMLEFBNERJLElBNURBLEFBa0RELFlBQVksQUFVVixPQUFPLEVBVlQsWUFBWSxBc0MvQ2YsT0FBTyxBdEN5REYsT0FBTyxFQTVEWixJQUFJLEFBa0RELFlBQVksQUFXVixPQUFPLEVBWFQsWUFBWSxBQVdWLE9BQU8sQXNDMURaLE9BQU8sQ3RDMERNLEVBQ1AsVUFBVSxFQUFFLE9BQStCLEVBQzNDLFlBQVksRUFBRSxPQUErQixFQUM3QyxLQUFLLEVqQjlERyxJQUFJLEdpQitEYjs7QUFqRUwsQUFtRU0sSUFuRUYsQUFrREQsWUFBWSxBQWdCVixRQUFRLEFBQ04sT0FBTyxFQWpCWCxZQUFZLEFBZ0JWLFFBQVEsQXNDL0RiLE9BQU8sQXRDZ0VBLE9BQU8sQ0FBQyxFQUNQLG1CQUFtQixFakJsRWIsSUFBSSxFaUJtRVYsaUJBQWlCLEVqQm5FWCxJQUFJLEdpQm9FWDs7QUF0RVAsQUEyRUUsSUEzRUUsQUEyRUQsWUFBWSxFQUFaLFlBQVksQXNDeEVmLE9BQU8sQ3RDd0VTLEViMUVkLFVBQVUsRUgyQkksT0FBTyxFRzFCckIsWUFBWSxFQUFFLE9BQWtCLEVBQ2hDLEtBQUssRUpETyxJQUFJLEdpQjJFZjs7QUE3RUgsQWJJRSxJYUpFLEFBMkVELFlBQVksQWJ2RVosTUFBTSxFYXVFTixZQUFZLEFzQ3hFZixPQUFPLEFuRENKLE1BQU0sQ0FBQyxFS0hSLFVBQVUsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFLLENSMkJULHNCQUFPLEdHdEJwQjs7QWFOSCxBYk9FLElhUEUsQUEyRUQsWUFBWSxBYnZFWixNQUFNLEVhdUVOLFlBQVksQXNDeEVmLE9BQU8sQW5EQ0osTUFBTSxFYUpULElBQUksQUEyRUQsWUFBWSxBYm5FWixNQUFNLEVhbUVOLFlBQVksQXNDeEVmLE9BQU8sQW5ES0osTUFBTSxDQUFDLEVBQ04sVUFBVSxFQUFFLE9BQWtCLEVBQzlCLFlBQVksRUFBRSxPQUFrQixFQUNoQyxLQUFLLEVKVEssSUFBSSxHSVVmOztBYVpILEFiYUUsSWFiRSxBQTJFRCxZQUFZLEFiOURaLE9BQU8sRWE4RFAsWUFBWSxBc0N4RWYsT0FBTyxBbkRVSixPQUFPLEVhYlYsSUFBSSxBQTJFRCxZQUFZLEFiN0RaLE9BQU8sRWE2RFAsWUFBWSxBYjdEWixPQUFPLEFtRFhWLE9BQU8sQ25EV0ksRUFDUCxVQUFVLEVBQUUsT0FBa0IsRUFDOUIsWUFBWSxFQUFFLE9BQW1CLEVBQ2pDLEtBQUssRUpmSyxJQUFJLEdJZ0JmOztBYWxCSCxBYm9CSSxJYXBCQSxBQTJFRCxZQUFZLEFieERaLFFBQVEsQUFDTixPQUFPLEVhdURULFlBQVksQWJ4RFosUUFBUSxBbURoQlgsT0FBTyxBbkRpQkYsT0FBTyxDQUFDLEVBQ1AsbUJBQW1CLEVKbkJYLElBQUksRUlvQlosaUJBQWlCLEVKcEJULElBQUksR0lxQmI7O0FhdkJMLEFBK0VFLElBL0VFLEFBK0VELFVBQVUsRUFBVixVQUFVLEFzQzVFYixPQUFPLEN0QzRFTyxFYjlFWixVQUFVLEVINkJFLE9BQU8sRUc1Qm5CLFlBQVksRUFBRSxPQUFrQixFQUNoQyxLQUFLLEVKRE8sSUFBSSxHaUIrRWY7O0FBakZILEFiSUUsSWFKRSxBQStFRCxVQUFVLEFiM0VWLE1BQU0sRWEyRU4sVUFBVSxBc0M1RWIsT0FBTyxBbkRDSixNQUFNLENBQUMsRUtIUixVQUFVLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBSyxDUjZCWCxxQkFBTyxHR3hCbEI7O0FhTkgsQWJPRSxJYVBFLEFBK0VELFVBQVUsQWIzRVYsTUFBTSxFYTJFTixVQUFVLEFzQzVFYixPQUFPLEFuRENKLE1BQU0sRWFKVCxJQUFJLEFBK0VELFVBQVUsQWJ2RVYsTUFBTSxFYXVFTixVQUFVLEFzQzVFYixPQUFPLEFuREtKLE1BQU0sQ0FBQyxFQUNOLFVBQVUsRUFBRSxPQUFrQixFQUM5QixZQUFZLEVBQUUsT0FBa0IsRUFDaEMsS0FBSyxFSlRLLElBQUksR0lVZjs7QWFaSCxBYmFFLElhYkUsQUErRUQsVUFBVSxBYmxFVixPQUFPLEVha0VQLFVBQVUsQXNDNUViLE9BQU8sQW5EVUosT0FBTyxFYWJWLElBQUksQUErRUQsVUFBVSxBYmpFVixPQUFPLEVhaUVQLFVBQVUsQWJqRVYsT0FBTyxBbURYVixPQUFPLENuRFdJLEVBQ1AsVUFBVSxFQUFFLE9BQWtCLEVBQzlCLFlBQVksRUFBRSxPQUFtQixFQUNqQyxLQUFLLEVKZkssSUFBSSxHSWdCZjs7QWFsQkgsQWJvQkksSWFwQkEsQUErRUQsVUFBVSxBYjVEVixRQUFRLEFBQ04sT0FBTyxFYTJEVCxVQUFVLEFiNURWLFFBQVEsQW1EaEJYLE9BQU8sQW5EaUJGLE9BQU8sQ0FBQyxFQUNQLG1CQUFtQixFSm5CWCxJQUFJLEVJb0JaLGlCQUFpQixFSnBCVCxJQUFJLEdJcUJiOztBYXZCTCxBQW9GRSxJQXBGRSxBQW9GRCxTQUFTLEVBQVQsU0FBUyxBc0NqRlosT0FBTyxDdENpRk0sRUFDVCxVQUFVLEVBQUUsV0FBVyxFQUN2QixZQUFZLEVBQUUsV0FBVyxFQUN6QixLQUFLLEVqQnZGTyxPQUFPLEdpQjhGcEI7O0FBOUZILEFBd0ZJLElBeEZBLEFBb0ZELFNBQVMsQUFJUCxNQUFNLEVBSlIsU0FBUyxBc0NqRlosT0FBTyxBdENxRkYsTUFBTSxFQXhGWCxJQUFJLEFBb0ZELFNBQVMsQUFLUCxNQUFNLEVBTFIsU0FBUyxBc0NqRlosT0FBTyxBdENzRkYsTUFBTSxFQXpGWCxJQUFJLEFBb0ZELFNBQVMsQUFNUCxPQUFPLEVBTlQsU0FBUyxBc0NqRlosT0FBTyxBdEN1RkYsT0FBTyxFQTFGWixJQUFJLEFBb0ZELFNBQVMsQUFPUCxPQUFPLEVBUFQsU0FBUyxBQU9QLE9BQU8sQXNDeEZaLE9BQU8sQ3RDd0ZNLEVBQ1AsS0FBSyxFaEJ0RE8sT0FBd0IsR2dCdURyQzs7QUE3RkwsQUFpR0UsSUFqR0UsQUFpR0QsT0FBTyxFQUFQLE9BQU8sQXNDOUZWLE9BQU8sQ3RDOEZJLEVBQ1AsU0FBUyxFaEIxQkUsTUFBSyxFZ0IyQmhCLE1BQU0sRWhCdENELE1BQU0sRWdCdUNYLE9BQU8sRWhCVlksT0FBcUQsQ0FIckQsTUFBYSxHZ0JjakM7O0FBckdILEFBdUdFLElBdkdFLEFBdUdELE9BQU8sRUFBUCxPQUFPLEFzQ3BHVixPQUFPLEN0Q29HSSxFQUNQLFNBQVMsRWhCL0JFLE1BQUssRWdCZ0NoQixNQUFNLEVoQnpDQSxJQUFJLEVnQjBDVixPQUFPLEVoQmZZLE9BQXFELENBSHJELE1BQWEsR2dCbUJqQzs7QUEzR0gsQUE4R0UsSUE5R0UsQUE4R0QsVUFBVSxFQUFWLFVBQVUsQXNDM0diLE9BQU8sQ3RDMkdPLEVBQ1YsT0FBTyxFQUFFLEtBQUssRUFDZCxLQUFLLEVBQUUsSUFBSSxHQUNaOztBQWpISCxBQW9IRSxJQXBIRSxBQW9IRCxXQUFXLEVBQVgsV0FBVyxBc0NqSGQsT0FBTyxDdENpSFEsRUFDWCxLQUFLLEVoQnREQSxNQUFNLEVnQnVEWCxZQUFZLEVBQUUsQ0FBQyxFQUNmLGFBQWEsRUFBRSxDQUFDLEdBU2pCOztBQWhJSCxBQXlISSxJQXpIQSxBQW9IRCxXQUFXLEFBS1QsT0FBTyxFQUxULFdBQVcsQUFLVCxPQUFPLEFzQ3RIWixPQUFPLEN0Q3NITSxFQUNQLEtBQUssRWhCN0RGLE1BQU0sR2dCOERWOztBQTNITCxBQTZISSxJQTdIQSxBQW9IRCxXQUFXLEFBU1QsT0FBTyxFQVRULFdBQVcsQUFTVCxPQUFPLEFzQzFIWixPQUFPLEN0QzBITSxFQUNQLEtBQUssRWhCOURELElBQUksR2dCK0RUOztBQS9ITCxBQW1JRSxJQW5JRSxBQW1JRCxVQUFVLEVBQVYsVUFBVSxBc0NoSWIsT0FBTyxDdENnSU8sRUFDVixVQUFVLEVBQUUsV0FBVyxFQUN2QixNQUFNLEVBQUUsQ0FBQyxFQUNULEtBQUssRUFBRSxZQUFZLEVBQ25CLE1BQU0sRWhCNUVELElBQUksRWdCNkVULFdBQVcsRWhCOUVOLE1BQUssRWdCK0VWLFdBQVcsRWhCbEZOLE1BQUssRWdCbUZWLFlBQVksRUFBRSxJQUFJLEVBQ2xCLE9BQU8sRUFBRSxDQUFDLEVBQ1YsT0FBTyxFaEJ0RkYsTUFBSyxFZ0J1RlYsZUFBZSxFQUFFLElBQUksRUFDckIsS0FBSyxFaEJuRkEsSUFBSSxHZ0I4RlY7O0FBekpILEFBZ0pJLElBaEpBLEFBbUlELFVBQVUsQUFhUixNQUFNLEVBYlIsVUFBVSxBc0NoSWIsT0FBTyxBdEM2SUYsTUFBTSxFQWhKWCxJQUFJLEFBbUlELFVBQVUsQUFjUixNQUFNLEVBZFIsVUFBVSxBc0NoSWIsT0FBTyxBdEM4SUYsTUFBTSxDQUFDLEVBQ04sVUFBVSxFakI1SUwsd0JBQXlCLEVpQjZJOUIsT0FBTyxFQUFFLEdBQUcsR0FDYjs7QUFwSkwsQUFzSkksSUF0SkEsQUFtSUQsVUFBVSxBQW1CUixRQUFRLEVBbkJWLFVBQVUsQXNDaEliLE9BQU8sQXRDbUpGLFFBQVEsQ0FBQyxFQUNSLE9BQU8sRUFBRSxPQUFPLEdBQ2pCOztBQUtMLEFBQUEsVUFBVSxDQUFDLEVBQ1QsT0FBTyxFQUFFLFdBQVcsRUFDcEIsU0FBUyxFQUFFLElBQUksR0FnQ2hCOztBQWxDRCxBQUlFLFVBSlEsQ0FJUixJQUFJLEVBSk4sVUFBVSxDc0MxSlYsT0FBTyxDdEM4SkEsRUFDSCxJQUFJLEVBQUUsUUFBUSxHQW9CZjs7QUF6QkgsQUFNSSxVQU5NLENBSVIsSUFBSSxBQUVELFlBQVksQUFBQSxJQUFLLENBQUEsV0FBVyxHQU5qQyxVQUFVLENzQzFKVixPQUFPLEF0Q2dLRixZQUFZLEFBQUEsSUFBSyxDQUFBLFdBQVcsRUFBRSxFQUM3QiwwQkFBMEIsRUFBRSxDQUFDLEVBQzdCLHVCQUF1QixFQUFFLENBQUMsR0FDM0I7O0FBVEwsQUFVSSxVQVZNLENBSVIsSUFBSSxBQU1ELElBQUssQ0FBQSxZQUFZLENBQUMsSUFBSyxDQUpOLFdBQVcsR0FOakMsVUFBVSxDc0MxSlYsT0FBTyxBdENvS0YsSUFBSyxDQUFBLFlBQVksQ0FBQyxJQUFLLENBSk4sV0FBVyxFQUlRLEVBQ25DLGFBQWEsRUFBRSxDQUFDLEVBQ2hCLFdBQVcsRWhCcEhSLFFBQU0sR2dCcUhWOztBQWJMLEFBY0ksVUFkTSxDQUlSLElBQUksQUFVRCxXQUFXLEFBQUEsSUFBSyxDQUpYLFlBQVksR0FWdEIsVUFBVSxDc0MxSlYsT0FBTyxBdEN3S0YsV0FBVyxBQUFBLElBQUssQ0FKWCxZQUFZLEVBSWEsRUFDN0IseUJBQXlCLEVBQUUsQ0FBQyxFQUM1QixzQkFBc0IsRUFBRSxDQUFDLEVBQ3pCLFdBQVcsRWhCekhSLFFBQU0sR2dCMEhWOztBQWxCTCxBQW1CSSxVQW5CTSxDQUlSLElBQUksQUFlRCxNQUFNLEVBbkJYLFVBQVUsQ3NDMUpWLE9BQU8sQXRDZ0JKLE1BQU0sRUEwSVQsVUFBVSxDQUlSLElBQUksQUFnQkQsTUFBTSxFQXBCWCxVQUFVLENzQzFKVixPQUFPLEF0Q29CSixNQUFNLEVBc0lULFVBQVUsQ0FJUixJQUFJLEFBaUJELE9BQU8sRUFyQlosVUFBVSxDc0MxSlYsT0FBTyxBdEN5QkosT0FBTyxFQWlJVixVQUFVLENBSVIsSUFBSSxBQWtCRCxPQUFPLEVBdEJaLFVBQVUsQ0FoSVAsT0FBTyxBc0MxQlYsT0FBTyxDdENnTE0sRUFDUCxPQUFPLEVoQnJFRixDQUFDLEdnQnNFUDs7QUF4QkwsQUEyQkUsVUEzQlEsQUEyQlAsZ0JBQWdCLENBQUMsRUFDaEIsT0FBTyxFQUFFLElBQUksR0FLZDs7QUFqQ0gsQUE4QkksVUE5Qk0sQUEyQlAsZ0JBQWdCLENBR2YsSUFBSSxFQTlCUixVQUFVLEFBMkJQLGdCQUFnQixDc0NyTG5CLE9BQU8sQ3RDd0xFLEVBQ0gsSUFBSSxFQUFFLEtBQUssR0FDWjs7QUM3TEwsQUFDRSxXQURTLEFBQ1IsSUFBSyxDRGtLYyxXQUFXLEVDbEtaLEVBQ2pCLGFBQWEsRWpCc0RSLE1BQUssR2lCckRYOztBTjhTSCxBQUFBLFFBQVEsQ00zU0MsRUFDUCxhQUFhLEVqQm1ETixNQUFLLEdpQmxEYjs7QU5zVEQsQUFBQSxNQUFNLENNcFRDLEVBQ0wsU0FBUyxFakI4REksTUFBSyxFaUI3RGxCLFdBQVcsRUFBRSxHQUFHLEVBQ2hCLGFBQWEsRWpCNkNOLE1BQUssR2lCNUNiOztBQUdELEFBQUEsV0FBVyxDQUFDLEVBQ1YsT0FBTyxFQUFFLEtBQUssRUFDZCxXQUFXLEVqQnVEQyxNQUFNLEVpQnREbEIsT0FBTyxFQUFFLE1BQWtDLENBQUMsQ0FBQyxHQVc5Qzs7QUFkRCxBQUtFLFdBTFMsQUFLUixTQUFTLENBQUMsRUFDVCxTQUFTLEVqQmlERSxNQUFLLEVpQmhEaEIsT0FBTyxFQUFFLE1BQXFDLENBQUMsQ0FBQyxHQUNqRDs7QUFSSCxBQVVFLFdBVlMsQUFVUixTQUFTLENBQUMsRUFDVCxTQUFTLEVqQjZDRSxNQUFLLEVpQjVDaEIsT0FBTyxFQUFFLE1BQXFDLENBQUMsQ0FBQyxHQUNqRDs7QUFJSCxBQUFBLFdBQVcsRXFDbkNYLGFBQWEsR0FBRSxBQUFBLGVBQUMsQ0FBZ0IsT0FBTyxBQUF2QixFQUF5QixLQUFLLEdBQUUsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRUFBeUIsUUFBUSxDckNtQ3RFLEVBQ1YsVUFBVSxFQUFFLElBQUksRUFDaEIsVUFBVSxFbEJsQ0UsSUFBSSxFa0JtQ2hCLGdCQUFnQixFQUFFLElBQUksRUFDdEIsTUFBTSxFakJlQyxPQUFNLENpQmZTLEtBQUssQ2pCakJULE9BQTBCLEVpQmtCNUMsYUFBYSxFakJlTixNQUFLLEVpQmRaLEtBQUssRWxCakNXLE9BQXdCLEVrQmtDeEMsT0FBTyxFQUFFLEtBQUssRUFDZCxTQUFTLEVqQjZCQyxNQUFLLEVpQjVCZixNQUFNLEVqQm9CQyxNQUFNLEVpQm5CYixXQUFXLEVqQjhCQyxNQUFNLEVpQjdCbEIsU0FBUyxFQUFFLElBQUksRUFDZixPQUFPLEVBQUUsSUFBSSxFQUNiLE9BQU8sRWpCMENXLE9BQWtELENBakM3RCxNQUFLLEVpQlJaLFFBQVEsRUFBRSxRQUFRLEVBQ2xCLFVBQVUsRUFBRSxxREFBcUQsRUFDakUsS0FBSyxFQUFFLElBQUksR0FnQ1o7O0FBaERELEFBaUJFLFdBakJTLEFBaUJSLE1BQU0sRXFDcERULGFBQWEsQXJDb0RWLE1BQU0sR3FDcERNLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVBQXlCLEtBQUssQXJDb0QzQyxNQUFNLEdxQ3BEdUMsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRUFBeUIsUUFBUSxBckNvRC9FLE1BQU0sQ0FBQyxFVGxEUixVQUFVLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBSyxDVERULHVCQUFPLEVrQnFEbkIsWUFBWSxFbEJyREEsT0FBTyxHa0JzRHBCOztBQXBCSCxBQXFCRSxXQXJCUyxBQXFCUixhQUFhLEVxQ3hEaEIsYUFBYSxBckN3RFYsYUFBYSxHcUN4REQsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRUFBeUIsS0FBSyxBckN3RDNDLGFBQWEsR3FDeERnQyxBQUFBLGVBQUMsQ0FBZ0IsT0FBTyxBQUF2QixFQUF5QixRQUFRLEFyQ3dEL0UsYUFBYSxDQUFDLEVBQ2IsS0FBSyxFbEJyREksT0FBeUIsR2tCc0RuQzs7QUF2QkgsQUEwQkUsV0ExQlMsQUEwQlIsU0FBUyxFQUFULFNBQVMsQXFDN0RaLGFBQWEsR0FBRSxBQUFBLGVBQUMsQ0FBZ0IsT0FBTyxBQUF2QixFQUF5QixLQUFLLEFyQzZEM0MsU0FBUyxHcUM3RG9DLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVBQXlCLFFBQVEsQXJDNkQvRSxTQUFTLENBQUMsRUFDVCxTQUFTLEVqQldFLE1BQUssRWlCVmhCLE1BQU0sRWpCREQsTUFBTSxFaUJFWCxPQUFPLEVqQjJCWSxPQUFxRCxDQUhyRCxNQUFhLEdpQnZCakM7O0FBOUJILEFBZ0NFLFdBaENTLEFBZ0NSLFNBQVMsRUFBVCxTQUFTLEFxQ25FWixhQUFhLEdBQUUsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRUFBeUIsS0FBSyxBckNtRTNDLFNBQVMsR3FDbkVvQyxBQUFBLGVBQUMsQ0FBZ0IsT0FBTyxBQUF2QixFQUF5QixRQUFRLEFyQ21FL0UsU0FBUyxDQUFDLEVBQ1QsU0FBUyxFakJNRSxNQUFLLEVpQkxoQixNQUFNLEVqQkpBLElBQUksRWlCS1YsT0FBTyxFakJzQlksT0FBcUQsQ0FIckQsTUFBYSxHaUJsQmpDOztBQXBDSCxBQXNDRSxXQXRDUyxBQXNDUixhQUFhLEVBQWIsYUFBYSxBcUN6RWhCLGFBQWEsR0FBRSxBQUFBLGVBQUMsQ0FBZ0IsT0FBTyxBQUF2QixFQUF5QixLQUFLLEFyQ3lFM0MsYUFBYSxHcUN6RWdDLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVBQXlCLFFBQVEsQXJDeUUvRSxhQUFhLENBQUMsRUFDYixPQUFPLEVBQUUsWUFBWSxFQUNyQixjQUFjLEVBQUUsTUFBTSxFQUN0QixLQUFLLEVBQUUsSUFBSSxHQUNaOztBQTFDSCxBQTZDRSxXQTdDUyxDQTZDUixBQUFBLElBQUMsQ0FBSyxNQUFNLEFBQVgsR3FDaEZKLGFBQWEsQ3JDZ0ZWLEFBQUEsSUFBQyxDQUFLLE1BQU0sQUFBWCxJcUNoRlcsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRUFBeUIsS0FBSyxDckNnRjNDLEFBQUEsSUFBQyxDQUFLLE1BQU0sQUFBWCxJcUNoRjRDLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVBQXlCLFFBQVEsQ3JDZ0YvRSxBQUFBLElBQUMsQ0FBSyxNQUFNLEFBQVgsRUFBYSxFQUNiLE1BQU0sRUFBRSxJQUFJLEdBQ2I7O0FBSUgsQUFDRSxRQURNLEFBQUEsV0FBVyxFQUFuQixRQUFRLEFxQ3RGUixhQUFhLEdBQW1DLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVBQXlCLFFBQVEsRXJDc0ZsRixRQUFRLEFBQUEsV0FBVyxBQUVoQixTQUFTLEVBRlosUUFBUSxBQUVMLFNBQVMsQXFDeEZaLGFBQWEsR0FBbUMsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRUFBeUIsUUFBUSxBckNtRS9FLFNBQVMsRUFtQlosUUFBUSxBQUFBLFdBQVcsQUFHaEIsU0FBUyxFQUhaLFFBQVEsQUFHTCxTQUFTLEFxQ3pGWixhQUFhLEdBQW1DLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVBQXlCLFFBQVEsQXJDNkQvRSxTQUFTLENBNEJDLEVBQ1QsTUFBTSxFQUFFLElBQUksR0FDYjs7QUFJSCxBQUFBLGdCQUFnQixDQUFDLEVBQ2YsS0FBSyxFbEI1Rk0sT0FBeUIsRWtCNkZwQyxTQUFTLEVqQnhCSSxNQUFLLEVpQnlCbEIsVUFBVSxFakIxQ0gsTUFBSyxHaUJxRGI7O0FBVEMsQUFBQSxZQUFZLENBTGQsZ0JBQWdCLEVBTWQsV0FBVyxHQU5iLGdCQUFnQixDQU1FLEVBQ2QsS0FBSyxFakJ6RU8sT0FBTyxHaUIwRXBCOztBQUVELEFBQUEsVUFBVSxDQVZaLGdCQUFnQixFQVdkLFNBQVMsR0FYWCxnQkFBZ0IsQ0FXQSxFQUNaLEtBQUssRWpCNUVLLE9BQU8sR2lCNkVsQjs7QUFJSCxBQUFBLFlBQVksQ0FBQyxFQUNYLFVBQVUsRUFBRSxJQUFJLEVBQ2hCLE1BQU0sRWpCNURDLE9BQU0sQ2lCNERTLEtBQUssQ2pCNUZULE9BQTBCLEVpQjZGNUMsYUFBYSxFakI1RE4sTUFBSyxFaUI2RFosS0FBSyxFQUFFLE9BQU8sRUFDZCxTQUFTLEVqQjdDQyxNQUFLLEVpQjhDZixNQUFNLEVqQnREQyxNQUFNLEVpQnVEYixXQUFXLEVqQjVDQyxNQUFNLEVpQjZDbEIsT0FBTyxFQUFFLElBQUksRUFDYixPQUFPLEVqQi9CVyxPQUFrRCxDQWpDN0QsTUFBSyxFaUJpRVosY0FBYyxFQUFFLE1BQU0sRUFDdEIsS0FBSyxFQUFFLElBQUksRUFDWCxVQUFVLEVsQnpIRSxJQUFJLEdrQjZKakI7O0FBaERELEFBYUUsWUFiVSxBQWFULE1BQU0sQ0FBQyxFVDNIUixVQUFVLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBSyxDVERULHVCQUFPLEVrQjhIbkIsWUFBWSxFbEI5SEEsT0FBTyxHa0IrSHBCOztBQWhCSCxBQWlCRSxZQWpCVSxBQWlCVCxZQUFZLENBQUMsRUFDWixPQUFPLEVBQUUsSUFBSSxHQUNkOztBQW5CSCxBQXNCRSxZQXRCVSxBQXNCVCxVQUFVLENBQUMsRUFDVixTQUFTLEVqQjlERSxNQUFLLEVpQitEaEIsTUFBTSxFakIxRUQsTUFBTSxFaUIyRVgsT0FBTyxFakI5Q1ksT0FBcUQsQ2lCOEN6QyxNQUE0QyxDakI5Q3hELE9BQXFELENBSHJELE1BQWEsR2lCa0RqQzs7QUExQkgsQUE0QkUsWUE1QlUsQUE0QlQsVUFBVSxDQUFDLEVBQ1YsU0FBUyxFakJuRUUsTUFBSyxFaUJvRWhCLE1BQU0sRWpCN0VBLElBQUksRWlCOEVWLE9BQU8sRWpCbkRZLE9BQXFELENpQm1EekMsTUFBNEMsQ2pCbkR4RCxPQUFxRCxDQUhyRCxNQUFhLEdpQnVEakM7O0FBaENILEFBbUNFLFlBbkNVLENBbUNULEFBQUEsSUFBQyxBQUFBLEdBbkNKLFlBQVksQ0FvQ1QsQUFBQSxRQUFDLEFBQUEsRUFBVSxFQUNWLE1BQU0sRUFBRSxJQUFJLEVBQ1osT0FBTyxFakI1RFMsT0FBa0QsQ0FqQzdELE1BQUssR2lCa0dYOztBQTNDSCxBQXdDSSxZQXhDUSxDQW1DVCxBQUFBLElBQUMsQUFBQSxFQUtBLE1BQU0sRUF4Q1YsWUFBWSxDQW9DVCxBQUFBLFFBQUMsQUFBQSxFQUlBLE1BQU0sQ0FBQyxFQUNMLE9BQU8sRWpCbEdKLE1BQUssQ0FDTCxNQUFLLEdpQmtHVDs7QUExQ0wsQUE0Q0UsWUE1Q1UsQUE0Q1QsSUFBSyxFQUFBLEFBQUEsUUFBQyxBQUFBLEVBQVUsSUFBSyxFQUFBLEFBQUEsSUFBQyxBQUFBLEdBQU8sRUFDNUIsVUFBVSxFbEIxSkEsSUFBSSxDa0IwSmMsNExBQTRMLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxPQUFNLENBQUMsYUFBYyxDQUFDLE1BQUssRUFDcFEsYUFBYSxFQUFFLE1BQXVDLEdBQ3ZEOztBQUlILEFBQUEsY0FBYyxFQUNkLGVBQWUsQ0FBQyxFQUNkLFFBQVEsRUFBRSxRQUFRLEdBV25COztBQWJELEFBSUUsY0FKWSxDQUlaLFVBQVUsRUFIWixlQUFlLENBR2IsVUFBVSxDQUFDLEVBQ1QsTUFBTSxFakIzRVUsTUFBSyxFaUI0RXJCLE1BQU0sRUFBRSxDQUFDLENqQi9FTyxPQUFrRCxFaUJnRmxFLFFBQVEsRUFBRSxRQUFRLEVBQ2xCLEdBQUcsRUFBRSxHQUFHLEVBQ1IsU0FBUyxFQUFFLGdCQUFnQixFQUMzQixLQUFLLEVqQmhGVyxNQUFLLEVpQmlGckIsT0FBTyxFakI5REEsQ0FBQyxHaUIrRFQ7O0FBR0gsQUFDRSxjQURZLENBQ1osVUFBVSxDQUFDLEVBQ1QsSUFBSSxFakI5SEMsT0FBTSxHaUIrSFo7O0FBSEgsQUFLRSxjQUxZLENBS1osV0FBVyxFQUxiLGNBQWMsQ3FDbExkLGFBQWEsRXJDa0xiLGNBQWMsRXFDbExDLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVBQXlCLEtBQUssR0FBL0IsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRXJDa0xoQixjQUFjLENxQ2xMMkIsS0FBSyxFckNrTDlDLGNBQWMsRXFDbExrQyxBQUFBLGVBQUMsQ0FBZ0IsT0FBTyxBQUF2QixFQUF5QixRQUFRLEdBQWxDLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVyQ2tMakQsY0FBYyxDcUNsTDRELFFBQVEsQ3JDdUxwRSxFQUNWLFlBQVksRUFBRSxNQUEyQyxHQUMxRDs7QUFHSCxBQUNFLGVBRGEsQ0FDYixVQUFVLENBQUMsRUFDVCxLQUFLLEVqQnhJQSxPQUFNLEdpQnlJWjs7QUFISCxBQUtFLGVBTGEsQ0FLYixXQUFXLEVBTGIsZUFBZSxDcUM1TGYsYUFBYSxFckM0TGIsZUFBZSxFcUM1TEEsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRUFBeUIsS0FBSyxHQUEvQixBQUFBLGVBQUMsQ0FBZ0IsT0FBTyxBQUF2QixFckM0TGhCLGVBQWUsQ3FDNUwwQixLQUFLLEVyQzRMOUMsZUFBZSxFcUM1TGlDLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVBQXlCLFFBQVEsR0FBbEMsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRXJDNExqRCxlQUFlLENxQzVMMkQsUUFBUSxDckNpTXBFLEVBQ1YsYUFBYSxFQUFFLE1BQTJDLEdBQzNEOztBQUlILEFBQUEsY0FBYyxFQUNkLFdBQVcsRUFDWCxZQUFZLENBQUMsRUFDWCxPQUFPLEVBQUUsS0FBSyxFQUNkLFdBQVcsRWpCaElDLE1BQU0sRWlCaUlsQixNQUFNLEVBQUUsTUFBc0MsQ0FBQyxDQUFDLEVBQ2hELFVBQVUsRWpCL0lILE1BQU0sRWlCZ0piLE9BQU8sRUFBRSxNQUF1QyxDakJySnpDLE1BQUssQ2lCcUp3RCxNQUF1QyxDQUFDLE1BQXlDLEVBQ3JKLFFBQVEsRUFBRSxRQUFRLEdBcUNuQjs7QUE3Q0QsQUFVRSxjQVZZLENBVVosS0FBSyxFQVRQLFdBQVcsQ0FTVCxLQUFLLEVBUlAsWUFBWSxDQVFWLEtBQUssQ0FBQyxFQUNKLElBQUksRUFBRSxnQkFBZ0IsRUFDdEIsTUFBTSxFQUFFLEdBQUcsRUFDWCxNQUFNLEVBQUUsSUFBSSxFQUNaLFFBQVEsRUFBRSxNQUFNLEVBQ2hCLFFBQVEsRUFBRSxRQUFRLEVBQ2xCLEtBQUssRUFBRSxHQUFHLEdBU1g7O0FBekJILEFBaUJJLGNBakJVLENBVVosS0FBSyxBQU9GLE1BQU0sR0FBRyxVQUFVLEVBaEJ4QixXQUFXLENBU1QsS0FBSyxBQU9GLE1BQU0sR0FBRyxVQUFVLEVBZnhCLFlBQVksQ0FRVixLQUFLLEFBT0YsTUFBTSxHQUFHLFVBQVUsQ0FBQyxFVHROdkIsVUFBVSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQUssQ1REVCx1QkFBTyxFa0J5TmpCLFlBQVksRWxCek5GLE9BQU8sR2tCME5sQjs7QUFwQkwsQUFxQkksY0FyQlUsQ0FVWixLQUFLLEFBV0YsUUFBUSxHQUFHLFVBQVUsRUFwQjFCLFdBQVcsQ0FTVCxLQUFLLEFBV0YsUUFBUSxHQUFHLFVBQVUsRUFuQjFCLFlBQVksQ0FRVixLQUFLLEFBV0YsUUFBUSxHQUFHLFVBQVUsQ0FBQyxFQUNyQixVQUFVLEVsQjVOQSxPQUFPLEVrQjZOakIsWUFBWSxFbEI3TkYsT0FBTyxHa0I4TmxCOztBQXhCTCxBQTJCRSxjQTNCWSxDQTJCWixVQUFVLEVBMUJaLFdBQVcsQ0EwQlQsVUFBVSxFQXpCWixZQUFZLENBeUJWLFVBQVUsQ0FBQyxFQUNULE1BQU0sRWpCN0tELE9BQU0sQ2lCNktXLEtBQUssQ2pCN01YLE9BQTBCLEVpQjhNMUMsTUFBTSxFQUFFLE9BQU8sRUFDZixPQUFPLEVBQUUsWUFBWSxFQUNyQixRQUFRLEVBQUUsUUFBUSxFQUNsQixVQUFVLEVBQUUscURBQXFELEdBQ2xFOztBQWpDSCxBQW9DRSxjQXBDWSxBQW9DWCxTQUFTLEVBbkNaLFdBQVcsQUFtQ1IsU0FBUyxFQWxDWixZQUFZLEFBa0NULFNBQVMsQ0FBQyxFQUNULFNBQVMsRWpCbktFLE1BQUssRWlCb0toQixNQUFNLEVBQUUsQ0FBQyxHQUNWOztBQXZDSCxBQXlDRSxjQXpDWSxBQXlDWCxTQUFTLEVBeENaLFdBQVcsQUF3Q1IsU0FBUyxFQXZDWixZQUFZLEFBdUNULFNBQVMsQ0FBQyxFQUNULFNBQVMsRWpCdktFLE1BQUssRWlCd0toQixNQUFNLEVBQUUsTUFBeUMsQ0FBQyxDQUFDLEdBQ3BEOztBQUdILEFBRUUsY0FGWSxDQUVaLFVBQVUsRUFEWixXQUFXLENBQ1QsVUFBVSxDQUFDLEVBQ1QsVUFBVSxFbEJ0UEEsSUFBSSxFa0J1UGQsTUFBTSxFakI3SlUsTUFBSyxFaUI4SnJCLElBQUksRUFBRSxDQUFDLEVBQ1AsR0FBRyxFQUFFLE1BQTJDLEVBQ2hELEtBQUssRWpCaEtXLE1BQUssR2lCaUt0Qjs7QUFSSCxBQVdJLGNBWFUsQ0FVWixLQUFLLEFBQ0YsT0FBTyxHQUFHLFVBQVUsRUFWekIsV0FBVyxDQVNULEtBQUssQUFDRixPQUFPLEdBQUcsVUFBVSxDQUFDLEVBQ3BCLFVBQVUsRWpCek9BLE9BQXFCLEdpQjBPaEM7O0FBR0wsQUFDRSxjQURZLENBQ1osVUFBVSxDQUFDLEVBQ1QsYUFBYSxFakJqTlIsTUFBSyxHaUJrTlg7O0FBSEgsQUFPTSxjQVBRLENBS1osS0FBSyxBQUNGLFFBQVEsR0FBRyxVQUFVLEFBQ25CLFFBQVEsQ0FBQyxFQUNSLGVBQWUsRUFBRSxXQUFXLEVBQzVCLE1BQU0sRWpCeE5MLE1BQUssQ2lCd05tQixLQUFLLENsQjVReEIsSUFBSSxFa0I2UVYsaUJBQWlCLEVBQUUsQ0FBQyxFQUNwQixnQkFBZ0IsRUFBRSxDQUFDLEVBQ25CLE9BQU8sRUFBRSxFQUFFLEVBQ1gsTUFBTSxFQUFFLEdBQUcsRUFDWCxJQUFJLEVBQUUsR0FBRyxFQUNULFdBQVcsRUFBRSxJQUFJLEVBQ2pCLFVBQVUsRUFBRSxJQUFJLEVBQ2hCLFFBQVEsRUFBRSxRQUFRLEVBQ2xCLEdBQUcsRUFBRSxHQUFHLEVBQ1IsU0FBUyxFQUFFLGFBQWEsRUFDeEIsS0FBSyxFQUFFLEdBQUcsR0FDWDs7QUFyQlAsQUF1QkksY0F2QlUsQ0FLWixLQUFLLEFBa0JGLGNBQWMsR0FBRyxVQUFVLENBQUMsRUFDM0IsVUFBVSxFbEI3UkEsT0FBTyxFa0I4UmpCLFlBQVksRWxCOVJGLE9BQU8sR2tCMFNsQjs7QUFyQ0wsQUEwQk0sY0ExQlEsQ0FLWixLQUFLLEFBa0JGLGNBQWMsR0FBRyxVQUFVLEFBR3pCLFFBQVEsQ0FBQyxFQUNSLFVBQVUsRWxCOVJKLElBQUksRWtCK1JWLE9BQU8sRUFBRSxFQUFFLEVBQ1gsTUFBTSxFQUFFLEdBQUcsRUFDWCxJQUFJLEVBQUUsR0FBRyxFQUNULFdBQVcsRUFBRSxJQUFJLEVBQ2pCLFVBQVUsRUFBRSxJQUFJLEVBQ2hCLFFBQVEsRUFBRSxRQUFRLEVBQ2xCLEdBQUcsRUFBRSxHQUFHLEVBQ1IsS0FBSyxFQUFFLElBQUksR0FDWjs7QUFJUCxBQUNFLFdBRFMsQ0FDVCxVQUFVLENBQUMsRUFDVCxhQUFhLEVBQUUsR0FBRyxHQUNuQjs7QUFISCxBQU9NLFdBUEssQ0FLVCxLQUFLLEFBQ0YsUUFBUSxHQUFHLFVBQVUsQUFDbkIsUUFBUSxDQUFDLEVBQ1IsVUFBVSxFbEJuVEosSUFBSSxFa0JvVFYsYUFBYSxFQUFFLEdBQUcsRUFDbEIsT0FBTyxFQUFFLEVBQUUsRUFDWCxNQUFNLEVBQUUsR0FBRyxFQUNYLElBQUksRUFBRSxHQUFHLEVBQ1QsUUFBUSxFQUFFLFFBQVEsRUFDbEIsR0FBRyxFQUFFLEdBQUcsRUFDUixTQUFTLEVBQUUscUJBQXFCLEVBQ2hDLEtBQUssRUFBRSxHQUFHLEdBQ1g7O0FBTVAsQUFBQSxZQUFZLENBQUMsRUFDWCxZQUFZLEVBQUUsSUFBOEIsR0FvQzdDOztBQXJDRCxBQUdFLFlBSFUsQ0FHVixVQUFVLENBQUMsRUFDVCxVQUFVLEVsQnJVRCxPQUF5QixFa0JzVWxDLGVBQWUsRUFBRSxXQUFXLEVBQzVCLGFBQWEsRUFBRSxPQUF1QixFQUN0QyxNQUFNLEVBQUUsTUFBMkIsRUFDbkMsSUFBSSxFQUFFLENBQUMsRUFDUCxHQUFHLEVBQUUsT0FBZ0QsRUFDckQsS0FBSyxFakJoUkEsTUFBTSxHaUI2Ulo7O0FBdkJILEFBV0ksWUFYUSxDQUdWLFVBQVUsQUFRUCxRQUFRLENBQUMsRUFDUixVQUFVLEVsQjlVRixJQUFJLEVrQitVWixhQUFhLEVBQUUsR0FBRyxFQUNsQixPQUFPLEVBQUUsRUFBRSxFQUNYLE9BQU8sRUFBRSxLQUFLLEVBQ2QsTUFBTSxFakIxUkgsTUFBSyxFaUIyUlIsSUFBSSxFQUFFLENBQUMsRUFDUCxRQUFRLEVBQUUsUUFBUSxFQUNsQixHQUFHLEVBQUUsQ0FBQyxFQUNOLFVBQVUsRUFBRSwrREFBK0QsRUFDM0UsS0FBSyxFakIvUkYsTUFBSyxHaUJnU1Q7O0FBdEJMLEFBMkJNLFlBM0JNLENBeUJWLEtBQUssQUFDRixRQUFRLEdBQUcsVUFBVSxBQUNuQixRQUFRLENBQUMsRUFDUixJQUFJLEVBQUUsSUFBSSxHQUNYOztBQTdCUCxBQWdDTSxZQWhDTSxDQXlCVixLQUFLLEFBTUYsT0FBTyxHQUFHLFVBQVUsQUFDbEIsUUFBUSxDQUFDLEVBQ1IsVUFBVSxFbEIvVlAsT0FBeUIsR2tCZ1c3Qjs7QUFNUCxBQUFBLFlBQVksQ0FBQyxFQUNYLE9BQU8sRUFBRSxJQUFJLEdBNERkOztBQTdERCxBQUdFLFlBSFUsQ0FHVixrQkFBa0IsQ0FBQyxFQUNqQixVQUFVLEVsQjFXSCxPQUF5QixFa0IyV2hDLE1BQU0sRWpCNVRELE9BQU0sQ2lCNFRXLEtBQUssQ2pCNVZYLE9BQTBCLEVpQjZWMUMsYUFBYSxFakI1VFIsTUFBSyxFaUI2VFYsV0FBVyxFakJ6U0QsTUFBTSxFaUIwU2hCLE9BQU8sRWpCM1JTLE9BQWtELENBakM3RCxNQUFLLEVpQjZUVixXQUFXLEVBQUUsTUFBTSxHQVdwQjs7QUFwQkgsQUFXSSxZQVhRLENBR1Ysa0JBQWtCLEFBUWYsU0FBUyxDQUFDLEVBQ1QsU0FBUyxFakJoVEEsTUFBSyxFaUJpVGQsT0FBTyxFakIvUlUsT0FBcUQsQ0FIckQsTUFBYSxHaUJtUy9COztBQWRMLEFBZ0JJLFlBaEJRLENBR1Ysa0JBQWtCLEFBYWYsU0FBUyxDQUFDLEVBQ1QsU0FBUyxFakJwVEEsTUFBSyxFaUJxVGQsT0FBTyxFakJuU1UsT0FBcUQsQ0FIckQsTUFBYSxHaUJ1Uy9COztBQW5CTCxBQXNCRSxZQXRCVSxDQXNCVixXQUFXLEVBdEJiLFlBQVksQ3FDN1daLGFBQWEsRXJDNldiLFlBQVksRXFDN1dHLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVBQXlCLEtBQUssR0FBL0IsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRXJDNldoQixZQUFZLENxQzdXNkIsS0FBSyxFckM2VzlDLFlBQVksRXFDN1dvQyxBQUFBLGVBQUMsQ0FBZ0IsT0FBTyxBQUF2QixFQUF5QixRQUFRLEdBQWxDLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVyQzZXakQsWUFBWSxDcUM3VzhELFFBQVEsRXJDNldsRixZQUFZLENBdUJWLFlBQVksQ0FBQyxFQUNYLElBQUksRUFBRSxRQUFRLEVBQ2QsS0FBSyxFQUFFLEVBQUUsR0FDVjs7QUExQkgsQUE0QkUsWUE1QlUsQ0E0QlYsZ0JBQWdCLENBQUMsRUFDZixPQUFPLEVqQjFSQSxDQUFDLEdpQjJSVDs7QUE5QkgsQUFvQ0ksWUFwQ1EsQ0FnQ1YsV0FBVyxBQUlSLFlBQVksQUFBQSxJQUFLLENEN09BLFdBQVcsR0N5TWpDLFlBQVksQ3FDN1daLGFBQWEsQXJDaVpSLFlBQVksQUFBQSxJQUFLLENEN09BLFdBQVcsR0N5TWpDLFlBQVksRXFDN1dHLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVBQXlCLEtBQUssQXJDaVp6QyxZQUFZLEFBQUEsSUFBSyxDRDdPQSxXQUFXLElzQ3BLbEIsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRXJDNldoQixZQUFZLENxQzdXNkIsS0FBSyxBckNpWnpDLFlBQVksQUFBQSxJQUFLLENEN09BLFdBQVcsR0N5TWpDLFlBQVksRXFDN1dvQyxBQUFBLGVBQUMsQ0FBZ0IsT0FBTyxBQUF2QixFQUF5QixRQUFRLEFyQ2laN0UsWUFBWSxBQUFBLElBQUssQ0Q3T0EsV0FBVyxJc0NwS2UsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRXJDNldqRCxZQUFZLENxQzdXOEQsUUFBUSxBckNpWjdFLFlBQVksQUFBQSxJQUFLLENEN09BLFdBQVcsR0N5TWpDLFlBQVksQ0FpQ1YsWUFBWSxBQUdULFlBQVksQUFBQSxJQUFLLENEN09BLFdBQVcsR0N5TWpDLFlBQVksQ0FrQ1Ysa0JBQWtCLEFBRWYsWUFBWSxBQUFBLElBQUssQ0Q3T0EsV0FBVyxHQ3lNakMsWUFBWSxDQW1DVixnQkFBZ0IsQUFDYixZQUFZLEFBQUEsSUFBSyxDRDdPQSxXQUFXLEVDNk9FLEVBQzdCLDBCQUEwQixFQUFFLENBQUMsRUFDN0IsdUJBQXVCLEVBQUUsQ0FBQyxHQUMzQjs7QUF2Q0wsQUF3Q0ksWUF4Q1EsQ0FnQ1YsV0FBVyxBQVFSLElBQUssQ0Q3T0EsWUFBWSxDQzZPQyxJQUFLLENEalBOLFdBQVcsR0N5TWpDLFlBQVksQ3FDN1daLGFBQWEsQXJDcVpSLElBQUssQ0Q3T0EsWUFBWSxDQzZPQyxJQUFLLENEalBOLFdBQVcsR0N5TWpDLFlBQVksRXFDN1dHLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVBQXlCLEtBQUssQXJDcVp6QyxJQUFLLENEN09BLFlBQVksQ0M2T0MsSUFBSyxDRGpQTixXQUFXLElzQ3BLbEIsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRXJDNldoQixZQUFZLENxQzdXNkIsS0FBSyxBckNxWnpDLElBQUssQ0Q3T0EsWUFBWSxDQzZPQyxJQUFLLENEalBOLFdBQVcsR0N5TWpDLFlBQVksRXFDN1dvQyxBQUFBLGVBQUMsQ0FBZ0IsT0FBTyxBQUF2QixFQUF5QixRQUFRLEFyQ3FaN0UsSUFBSyxDRDdPQSxZQUFZLENDNk9DLElBQUssQ0RqUE4sV0FBVyxJc0NwS2UsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRXJDNldqRCxZQUFZLENxQzdXOEQsUUFBUSxBckNxWjdFLElBQUssQ0Q3T0EsWUFBWSxDQzZPQyxJQUFLLENEalBOLFdBQVcsR0N5TWpDLFlBQVksQ0FpQ1YsWUFBWSxBQU9ULElBQUssQ0Q3T0EsWUFBWSxDQzZPQyxJQUFLLENEalBOLFdBQVcsR0N5TWpDLFlBQVksQ0FrQ1Ysa0JBQWtCLEFBTWYsSUFBSyxDRDdPQSxZQUFZLENDNk9DLElBQUssQ0RqUE4sV0FBVyxHQ3lNakMsWUFBWSxDQW1DVixnQkFBZ0IsQUFLYixJQUFLLENEN09BLFlBQVksQ0M2T0MsSUFBSyxDRGpQTixXQUFXLEVDaVBRLEVBQ25DLGFBQWEsRUFBRSxDQUFDLEVBQ2hCLFdBQVcsRWpCaldSLFFBQU0sR2lCa1dWOztBQTNDTCxBQTRDSSxZQTVDUSxDQWdDVixXQUFXLEFBWVIsV0FBVyxBQUFBLElBQUssQ0RqUFgsWUFBWSxHQ3FNdEIsWUFBWSxDcUM3V1osYUFBYSxBckN5WlIsV0FBVyxBQUFBLElBQUssQ0RqUFgsWUFBWSxHQ3FNdEIsWUFBWSxFcUM3V0csQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRUFBeUIsS0FBSyxBckN5WnpDLFdBQVcsQUFBQSxJQUFLLENEalBYLFlBQVksSXNDeEtQLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVyQzZXaEIsWUFBWSxDcUM3VzZCLEtBQUssQXJDeVp6QyxXQUFXLEFBQUEsSUFBSyxDRGpQWCxZQUFZLEdDcU10QixZQUFZLEVxQzdXb0MsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRUFBeUIsUUFBUSxBckN5WjdFLFdBQVcsQUFBQSxJQUFLLENEalBYLFlBQVksSXNDeEswQixBQUFBLGVBQUMsQ0FBZ0IsT0FBTyxBQUF2QixFckM2V2pELFlBQVksQ3FDN1c4RCxRQUFRLEFyQ3laN0UsV0FBVyxBQUFBLElBQUssQ0RqUFgsWUFBWSxHQ3FNdEIsWUFBWSxDQWlDVixZQUFZLEFBV1QsV0FBVyxBQUFBLElBQUssQ0RqUFgsWUFBWSxHQ3FNdEIsWUFBWSxDQWtDVixrQkFBa0IsQUFVZixXQUFXLEFBQUEsSUFBSyxDRGpQWCxZQUFZLEdDcU10QixZQUFZLENBbUNWLGdCQUFnQixBQVNiLFdBQVcsQUFBQSxJQUFLLENEalBYLFlBQVksRUNpUGEsRUFDN0IseUJBQXlCLEVBQUUsQ0FBQyxFQUM1QixzQkFBc0IsRUFBRSxDQUFDLEVBQ3pCLFdBQVcsRWpCdFdSLFFBQU0sR2lCdVdWOztBQWhETCxBQWlESSxZQWpEUSxDQWdDVixXQUFXLEFBaUJSLE1BQU0sRUFqRFgsWUFBWSxDcUM3V1osYUFBYSxBckNvRFYsTUFBTSxFQXlUVCxZQUFZLEVxQzdXRyxBQUFBLGVBQUMsQ0FBZ0IsT0FBTyxBQUF2QixFQUF5QixLQUFLLEFyQ29EM0MsTUFBTSxHcUNwRE0sQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRXJDNldoQixZQUFZLENxQzdXNkIsS0FBSyxBckNvRDNDLE1BQU0sRUF5VFQsWUFBWSxFcUM3V29DLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVBQXlCLFFBQVEsQXJDb0QvRSxNQUFNLEdxQ3BEdUMsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRXJDNldqRCxZQUFZLENxQzdXOEQsUUFBUSxBckNvRC9FLE1BQU0sRUF5VFQsWUFBWSxDQWlDVixZQUFZLEFBZ0JULE1BQU0sRUFqRFgsWUFBWSxDQWtDVixrQkFBa0IsQUFlZixNQUFNLEVBakRYLFlBQVksQ0FtQ1YsZ0JBQWdCLEFBY2IsTUFBTSxDQUFDLEVBQ04sT0FBTyxFakIvU0YsQ0FBQyxHaUJnVFA7O0FBbkRMLEFBc0RFLFlBdERVLENBc0RWLFlBQVksQ0FBQyxFQUNYLEtBQUssRUFBRSxJQUFJLEdBQ1o7O0FBeERILEFBMERFLFlBMURVLEFBMERULGFBQWEsQ0FBQyxFQUNiLE9BQU8sRUFBRSxXQUFXLEdBQ3JCOztBQU1ELEFBQUEsWUFBWSxDQUZkLFdBQVcsRUFFVCxZQUFZLENxQy9hZCxhQUFhLEVyQythWCxZQUFZLEVxQy9hQyxBQUFBLGVBQUMsQ0FBZ0IsT0FBTyxBQUF2QixFQUF5QixLQUFLLEdBQS9CLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVyQythZCxZQUFZLENxQy9hMkIsS0FBSyxFckMrYTVDLFlBQVksRXFDL2FrQyxBQUFBLGVBQUMsQ0FBZ0IsT0FBTyxBQUF2QixFQUF5QixRQUFRLEdBQWxDLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVyQythL0MsWUFBWSxDcUMvYTRELFFBQVEsRXJDNmFsRixXQUFXLEFBR1IsV0FBVyxFQUFYLFdBQVcsQXFDaGJkLGFBQWEsR0FBRSxBQUFBLGVBQUMsQ0FBZ0IsT0FBTyxBQUF2QixFQUF5QixLQUFLLEFyQ2diM0MsV0FBVyxHcUNoYmtDLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVBQXlCLFFBQVEsQXJDZ2IvRSxXQUFXLEVBRFosWUFBWSxDQURkLFlBQVksRUFBWixZQUFZLEFBRVQsV0FBVyxDQUFDLEVBQ1gsVUFBVSxFQUFFLE9BQTRCLEVBQ3hDLFlBQVksRWpCclpBLE9BQU8sR2lCeVpwQjs7QUFQRCxBQUlFLFlBSlUsQ0FGZCxXQUFXLEFBTU4sTUFBTSxFQUpULFlBQVksQ3FDL2FkLGFBQWEsQXJDb0RWLE1BQU0sRUEyWFAsWUFBWSxFcUMvYUMsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRUFBeUIsS0FBSyxBckNvRDNDLE1BQU0sR3FDcERNLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVyQythZCxZQUFZLENxQy9hMkIsS0FBSyxBckNvRDNDLE1BQU0sRUEyWFAsWUFBWSxFcUMvYWtDLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVBQXlCLFFBQVEsQXJDb0QvRSxNQUFNLEdxQ3BEdUMsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRXJDK2EvQyxZQUFZLENxQy9hNEQsUUFBUSxBckNvRC9FLE1BQU0sRUF5WFQsV0FBVyxBQUdSLFdBQVcsQUFHVCxNQUFNLEVBSFIsV0FBVyxBcUNoYmQsYUFBYSxBckNtYlIsTUFBTSxHcUNuYkksQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRUFBeUIsS0FBSyxBckNnYjNDLFdBQVcsQUFHVCxNQUFNLEdxQ25icUMsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRUFBeUIsUUFBUSxBckNnYi9FLFdBQVcsQUFHVCxNQUFNLEVBSlQsWUFBWSxDQURkLFlBQVksQUFLUCxNQUFNLEVBTFgsWUFBWSxBQUVULFdBQVcsQUFHVCxNQUFNLENBQUMsRVRqYlYsVUFBVSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQUssQ1IyQlQsc0JBQU8sR2lCd1psQjs7QUFHSCxBQUFBLFVBQVUsQ0FYWixXQUFXLEVBV1QsVUFBVSxDcUN4YlosYUFBYSxFckN3YlgsVUFBVSxFcUN4YkcsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRUFBeUIsS0FBSyxHQUEvQixBQUFBLGVBQUMsQ0FBZ0IsT0FBTyxBQUF2QixFckN3YmQsVUFBVSxDcUN4YjZCLEtBQUssRXJDd2I1QyxVQUFVLEVxQ3hib0MsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRUFBeUIsUUFBUSxHQUFsQyxBQUFBLGVBQUMsQ0FBZ0IsT0FBTyxBQUF2QixFckN3Yi9DLFVBQVUsQ3FDeGI4RCxRQUFRLEVyQzZhbEYsV0FBVyxBQVlSLFNBQVMsRUFBVCxTQUFTLEFxQ3piWixhQUFhLEdBQUUsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRUFBeUIsS0FBSyxBckN5YjNDLFNBQVMsR3FDemJvQyxBQUFBLGVBQUMsQ0FBZ0IsT0FBTyxBQUF2QixFQUF5QixRQUFRLEFyQ3liL0UsU0FBUyxFQURWLFVBQVUsQ0FWWixZQUFZLEVBQVosWUFBWSxBQVdULFNBQVMsQ0FBQyxFQUNULFVBQVUsRUFBRSxPQUEwQixFQUN0QyxZQUFZLEVqQjVaRixPQUFPLEdpQmdhbEI7O0FBUEQsQUFJRSxVQUpRLENBWFosV0FBVyxBQWVOLE1BQU0sRUFKVCxVQUFVLENxQ3hiWixhQUFhLEFyQ29EVixNQUFNLEVBb1lQLFVBQVUsRXFDeGJHLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVBQXlCLEtBQUssQXJDb0QzQyxNQUFNLEdxQ3BETSxBQUFBLGVBQUMsQ0FBZ0IsT0FBTyxBQUF2QixFckN3YmQsVUFBVSxDcUN4YjZCLEtBQUssQXJDb0QzQyxNQUFNLEVBb1lQLFVBQVUsRXFDeGJvQyxBQUFBLGVBQUMsQ0FBZ0IsT0FBTyxBQUF2QixFQUF5QixRQUFRLEFyQ29EL0UsTUFBTSxHcUNwRHVDLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVyQ3diL0MsVUFBVSxDcUN4YjhELFFBQVEsQXJDb0QvRSxNQUFNLEVBeVhULFdBQVcsQUFZUixTQUFTLEFBR1AsTUFBTSxFQUhSLFNBQVMsQXFDemJaLGFBQWEsQXJDNGJSLE1BQU0sR3FDNWJJLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVBQXlCLEtBQUssQXJDeWIzQyxTQUFTLEFBR1AsTUFBTSxHcUM1YnFDLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVBQXlCLFFBQVEsQXJDeWIvRSxTQUFTLEFBR1AsTUFBTSxFQUpULFVBQVUsQ0FWWixZQUFZLEFBY1AsTUFBTSxFQWRYLFlBQVksQUFXVCxTQUFTLEFBR1AsTUFBTSxDQUFDLEVUMWJWLFVBQVUsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFLLENSNkJYLHFCQUFPLEdpQitaaEI7O0FBT0gsQUFFRSxVQUZRLENBSFosY0FBYyxDQUtWLFVBQVUsRUFMZCxjQUFjLEFBSVgsU0FBUyxDQUNSLFVBQVUsRUFGWixVQUFVLENBRlosV0FBVyxDQUlQLFVBQVUsRUFKZCxXQUFXLEFBR1IsU0FBUyxDQUNSLFVBQVUsRUFGWixVQUFVLENBRFosWUFBWSxDQUdSLFVBQVUsRUFIZCxZQUFZLEFBRVQsU0FBUyxDQUNSLFVBQVUsQ0FBQyxFQUNULFlBQVksRWpCemFKLE9BQU8sR2lCMGFoQjs7QUFKSCxBQU9JLFVBUE0sQ0FIWixjQUFjLENBU1YsS0FBSyxBQUNGLFFBQVEsR0FBRyxVQUFVLEVBVjVCLGNBQWMsQUFJWCxTQUFTLENBS1IsS0FBSyxBQUNGLFFBQVEsR0FBRyxVQUFVLEVBUDFCLFVBQVUsQ0FGWixXQUFXLENBUVAsS0FBSyxBQUNGLFFBQVEsR0FBRyxVQUFVLEVBVDVCLFdBQVcsQUFHUixTQUFTLENBS1IsS0FBSyxBQUNGLFFBQVEsR0FBRyxVQUFVLEVBUDFCLFVBQVUsQ0FEWixZQUFZLENBT1IsS0FBSyxBQUNGLFFBQVEsR0FBRyxVQUFVLEVBUjVCLFlBQVksQUFFVCxTQUFTLENBS1IsS0FBSyxBQUNGLFFBQVEsR0FBRyxVQUFVLENBQUMsRUFDckIsVUFBVSxFakI5YUosT0FBTyxFaUIrYWIsWUFBWSxFakIvYU4sT0FBTyxHaUJnYmQ7O0FBVkwsQUFZSSxVQVpNLENBSFosY0FBYyxDQVNWLEtBQUssQUFNRixNQUFNLEdBQUcsVUFBVSxFQWYxQixjQUFjLEFBSVgsU0FBUyxDQUtSLEtBQUssQUFNRixNQUFNLEdBQUcsVUFBVSxFQVp4QixVQUFVLENBRlosV0FBVyxDQVFQLEtBQUssQUFNRixNQUFNLEdBQUcsVUFBVSxFQWQxQixXQUFXLEFBR1IsU0FBUyxDQUtSLEtBQUssQUFNRixNQUFNLEdBQUcsVUFBVSxFQVp4QixVQUFVLENBRFosWUFBWSxDQU9SLEtBQUssQUFNRixNQUFNLEdBQUcsVUFBVSxFQWIxQixZQUFZLEFBRVQsU0FBUyxDQUtSLEtBQUssQUFNRixNQUFNLEdBQUcsVUFBVSxDQUFDLEVUL2N6QixVQUFVLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBSyxDUjZCWCxxQkFBTyxFaUJvYmIsWUFBWSxFakJwYk4sT0FBTyxHaUJxYmQ7O0FBTUwsQUFHSSxVQUhNLENBRFosY0FBYyxDQUdWLEtBQUssQUFDRixjQUFjLEdBQUcsVUFBVSxFQUpsQyxjQUFjLEFBRVgsU0FBUyxDQUNSLEtBQUssQUFDRixjQUFjLEdBQUcsVUFBVSxDQUFDLEVBQzNCLFVBQVUsRWpCL2JKLE9BQU8sRWlCZ2NiLFlBQVksRWpCaGNOLE9BQU8sR2lCaWNkOztBQU1QLEFBRUksV0FGTyxBQUNSLElBQUssQ0FBQSxrQkFBa0IsQ0FDckIsUUFBUSxFcUN4ZWIsYUFBYSxBckN1ZVYsSUFBSyxDQUFBLGtCQUFrQixDQUNyQixRQUFRLEdxQ3hlRSxBQUFBLGVBQUMsQ0FBZ0IsT0FBTyxBQUF2QixFQUF5QixLQUFLLEFyQ3VlM0MsSUFBSyxDQUFBLGtCQUFrQixDQUNyQixRQUFRLEdxQ3hlbUMsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRUFBeUIsUUFBUSxBckN1ZS9FLElBQUssQ0FBQSxrQkFBa0IsQ0FDckIsUUFBUSxDQUFDLEVBQ1IsWUFBWSxFakIxY0osT0FBTyxHaUJtZGhCOztBQVpMLEFBSU0sV0FKSyxBQUNSLElBQUssQ0FBQSxrQkFBa0IsQ0FDckIsUUFBUSxBQUVOLE1BQU0sRXFDMWViLGFBQWEsQXJDdWVWLElBQUssQ0FBQSxrQkFBa0IsQ0FDckIsUUFBUSxBQUVOLE1BQU0sR3FDMWVFLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVBQXlCLEtBQUssQXJDdWUzQyxJQUFLLENBQUEsa0JBQWtCLENBQ3JCLFFBQVEsQUFFTixNQUFNLEdxQzFlbUMsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRUFBeUIsUUFBUSxBckN1ZS9FLElBQUssQ0FBQSxrQkFBa0IsQ0FDckIsUUFBUSxBQUVOLE1BQU0sQ0FBQyxFVHhlWixVQUFVLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBSyxDUjZCWCxxQkFBTyxFaUI2Y2IsVUFBVSxFQUFFLE9BQTBCLEdBQ3ZDOztBQVBQLEFBU00sV0FUSyxBQUNSLElBQUssQ0FBQSxrQkFBa0IsQ0FDckIsUUFBUSxHQU9ILGdCQUFnQixFcUMvZTFCLGFBQWEsQXJDdWVWLElBQUssQ0FBQSxrQkFBa0IsQ0FDckIsUUFBUSxHQU9ILGdCQUFnQixHcUMvZVgsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRUFBeUIsS0FBSyxBckN1ZTNDLElBQUssQ0FBQSxrQkFBa0IsQ0FDckIsUUFBUSxHQU9ILGdCQUFnQixHcUMvZXNCLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVBQXlCLFFBQVEsQXJDdWUvRSxJQUFLLENBQUEsa0JBQWtCLENBQ3JCLFFBQVEsR0FPSCxnQkFBZ0IsQ0FBQyxFQUNuQixLQUFLLEVqQmpkQyxPQUFPLEdpQmtkZDs7QUFNUCxBQUVFLFdBRlMsQUFFUixTQUFTLEVxQ3pmWixhQUFhLEFyQ3lmVixTQUFTLEdxQ3pmRyxBQUFBLGVBQUMsQ0FBZ0IsT0FBTyxBQUF2QixFQUF5QixLQUFLLEFyQ3lmM0MsU0FBUyxHcUN6Zm9DLEFBQUEsZUFBQyxDQUFnQixPQUFPLEFBQXZCLEVBQXlCLFFBQVEsQXJDeWYvRSxTQUFTLEVBRlosV0FBVyxBQUdSLFNBQVMsRUFBVCxTQUFTLEFxQzFmWixhQUFhLEdBQUUsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRUFBeUIsS0FBSyxBckMwZjNDLFNBQVMsR3FDMWZvQyxBQUFBLGVBQUMsQ0FBZ0IsT0FBTyxBQUF2QixFQUF5QixRQUFRLEFyQzBmL0UsU0FBUyxFQUZaLFlBQVksQUFDVCxTQUFTLEVBRFosWUFBWSxBQUVULFNBQVMsQ0FBQyxFQUNULGdCQUFnQixFakJsZUosT0FBcUIsRWlCbWVqQyxNQUFNLEVBQUUsV0FBVyxFQUNuQixPQUFPLEVBQUUsRUFBRSxHQUNaOztBQUdILEFBQ0UsV0FEUyxDQUNSLEFBQUEsUUFBQyxBQUFBLEdxQ2xnQkosYUFBYSxDckNrZ0JWLEFBQUEsUUFBQyxBQUFBLElxQ2xnQlcsQUFBQSxlQUFDLENBQWdCLE9BQU8sQUFBdkIsRUFBeUIsS0FBSyxDckNrZ0IzQyxBQUFBLFFBQUMsQUFBQSxJcUNsZ0I0QyxBQUFBLGVBQUMsQ0FBZ0IsT0FBTyxBQUF2QixFQUF5QixRQUFRLENyQ2tnQi9FLEFBQUEsUUFBQyxBQUFBLEVBQVUsRUFDVixnQkFBZ0IsRWxCNWZULE9BQXlCLEdrQjZmakM7O0FBR0gsQUFHSSxLQUhDLEFBQ0YsU0FBUyxHQUVKLFVBQVUsRUFIbEIsS0FBSyxBQUVGLFNBQVMsR0FDSixVQUFVLENBQUMsRUFDYixVQUFVLEVqQmxmQSxPQUFxQixFaUJtZi9CLE1BQU0sRUFBRSxXQUFXLEVBQ25CLE9BQU8sRUFBRSxFQUFFLEdBQ1o7O0FBSUwsQUFJTSxZQUpNLENBQ1YsS0FBSyxBQUNGLFNBQVMsR0FFSixVQUFVLEFBQUEsUUFBUSxFQUo1QixZQUFZLENBQ1YsS0FBSyxBQUVGLFNBQVMsR0FDSixVQUFVLEFBQUEsUUFBUSxDQUFDLEVBQ3JCLFVBQVUsRWxCcGhCSixJQUFJLEdrQnFoQlg7O0FBTVAsQUFBQSxnQkFBZ0IsQ0FBQyxFQUNmLE9BQU8sRWpCdGVBLE1BQUssQ2lCc2VhLENBQUMsR0FNM0I7O0FBUEQsQUFHRSxnQkFIYyxDQUdkLFdBQVcsQ0FBQyxFQUNWLE9BQU8sRUFBRSxJQUFJLEVBQ2IsU0FBUyxFQUFFLElBQUksR0FDaEI7O0FBSUgsQUFBQSxZQUFZLENBQUMsRUFDWCxPQUFPLEVBQUUsWUFBWSxHQUN0Qjs7QUN6aUJELEFBQUEsTUFBTSxDQUFDLEVaQ0wsYUFBYSxFTnFETixNQUFLLEVNcERaLFdBQVcsRUFBRSxJQUFJLEVBQ2pCLE9BQU8sRUFBRSxXQUFXLEVBSXBCLFVBQVUsRU5pQkksT0FBcUIsRU1oQm5DLEtBQUssRVlOa0IsT0FBNkIsRUFDcEQsT0FBTyxFQUFFLFlBQVksR0E2QnRCOztBQWhDRCxBQU1FLE1BTkksQUFNSCxjQUFjLENBQUMsRUFDZCxhQUFhLEVBQUUsSUFBSSxFQUNuQixZQUFZLEVBQUUsS0FBSyxFQUNuQixhQUFhLEVBQUUsS0FBSyxHQUNyQjs7QUFWSCxBQWFFLE1BYkksQUFhSCxjQUFjLENBQUMsRVpOaEIsVUFBVSxFUFBJLE9BQU8sRU9RckIsS0FBSyxFUE5PLElBQUksR21CYWY7O0FBZkgsQUFpQkUsTUFqQkksQUFpQkgsZ0JBQWdCLENBQUMsRVpWbEIsVUFBVSxFTkVNLE9BQThCLEVNRDlDLEtBQUssRVBSUyxPQUFPLEdtQm1CcEI7O0FBbkJILEFBcUJFLE1BckJJLEFBcUJILGNBQWMsQ0FBQyxFWmRoQixVQUFVLEVOcUJJLE9BQU8sRU1wQnJCLEtBQUssRVBOTyxJQUFJLEdtQnFCZjs7QUF2QkgsQUF5QkUsTUF6QkksQUF5QkgsY0FBYyxDQUFDLEVabEJoQixVQUFVLEVOc0JJLE9BQU8sRU1yQnJCLEtBQUssRVBOTyxJQUFJLEdtQnlCZjs7QUEzQkgsQUE2QkUsTUE3QkksQUE2QkgsWUFBWSxDQUFDLEVadEJkLFVBQVUsRU51QkUsT0FBTyxFTXRCbkIsS0FBSyxFUE5PLElBQUksR21CNkJmOztBQy9CSCxBQUFBLElBQUksQ0FBQyxFYkNILGFBQWEsRU5xRE4sTUFBSyxFTXBEWixXQUFXLEVBQUUsSUFBSSxFQUNqQixPQUFPLEVBQUUsV0FBVyxFQUlwQixVQUFVLEVhTDBCLE9BQTJCLEViTS9ELEtBQUssRU55Qk0sT0FBTyxFbUI5QmxCLFNBQVMsRUFBRSxHQUFHLEdBQ2Y7O0FBRUQsQUFBQSxLQUFLLENBQUMsRUFDSixhQUFhLEVuQitDTixNQUFLLEVtQjlDWixLQUFLLEVwQkRXLE9BQXdCLEVvQkV4QyxRQUFRLEVBQUUsUUFBUSxHQW9CbkI7O0FBdkJELEFBS0UsS0FMRyxBQUtGLFFBQVEsQ0FBQyxFQUNSLEtBQUssRXBCVEksT0FBeUIsRW9CVWxDLE9BQU8sRUFBRSxlQUFlLEVBQ3hCLFNBQVMsRW5CMERFLE1BQUssRW1CekRoQixRQUFRLEVBQUUsUUFBUSxFQUNsQixLQUFLLEVuQndDQSxNQUFLLEVtQnZDVixHQUFHLEVuQnFDRSxNQUFLLEdtQnBDWDs7QUFaSCxBQWNFLEtBZEcsQ0FjSCxJQUFJLENBQUMsRUFDSCxVQUFVLEVwQmZILE9BQXlCLEVvQmdCaEMsS0FBSyxFQUFFLE9BQU8sRUFDZCxPQUFPLEVBQUUsS0FBSyxFQUNkLFdBQVcsRUFBRSxHQUFHLEVBQ2hCLFVBQVUsRUFBRSxJQUFJLEVBQ2hCLE9BQU8sRUFBRSxJQUFJLEVBQ2IsS0FBSyxFQUFFLElBQUksR0FDWjs7QUMzQkgsQUFBQSxlQUFlLENBQUMsRUFDZCxPQUFPLEVBQUUsS0FBSyxFQUNkLE1BQU0sRUFBRSxJQUFJLEVBQ1osU0FBUyxFQUFFLElBQUksR0FDaEI7O0FBSUQsQUFBQSxjQUFjLENBQUMsRUFDYixVQUFVLEVBQUUsS0FBSyxHQUNsQjs7QUFFRCxBQUFBLGdCQUFnQixDQUFDLEVBQ2YsVUFBVSxFQUFFLE9BQU8sR0FDcEI7O0FBR0QsQUFBQSxpQkFBaUIsQ0FBQyxFQUNoQixPQUFPLEVBQUUsS0FBSyxFQUNkLFFBQVEsRUFBRSxNQUFNLEVBQ2hCLE9BQU8sRUFBRSxDQUFDLEVBQ1YsUUFBUSxFQUFFLFFBQVEsRUFDbEIsS0FBSyxFQUFFLElBQUksR0FtQlo7O0FBeEJELEFBTUUsaUJBTmUsQUFNZCxRQUFRLENBQUMsRUFDUixPQUFPLEVBQUUsRUFBRSxFQUNYLE9BQU8sRUFBRSxLQUFLLEVBQ2QsY0FBYyxFQUFFLE1BQU0sR0FDdkI7O0FBVkgsQUFZRSxpQkFaZSxDQVlmLE1BQU0sRUFaUixpQkFBaUIsQ0FhZixNQUFNLEVBYlIsaUJBQWlCLENBY2YsS0FBSyxDQUFDLEVBQ0osTUFBTSxFQUFFLENBQUMsRUFDVCxNQUFNLEVBQUUsQ0FBQyxFQUNULE1BQU0sRUFBRSxJQUFJLEVBQ1osSUFBSSxFQUFFLENBQUMsRUFDUCxRQUFRLEVBQUUsUUFBUSxFQUNsQixLQUFLLEVBQUUsQ0FBQyxFQUNSLEdBQUcsRUFBRSxDQUFDLEVBQ04sS0FBSyxFQUFFLElBQUksR0FDWjs7QUFHSCxBQUFBLEtBQUssQUFBQSxpQkFBaUIsQ0FBQyxFQUNyQixNQUFNLEVBQUUsSUFBSSxFQUNaLFNBQVMsRUFBRSxJQUFJLEdBS2hCOztBQVBELEFBSUUsS0FKRyxBQUFBLGlCQUFpQixBQUluQixRQUFRLENBQUMsRUFDUixPQUFPLEVBQUUsSUFBSSxHQUNkOztBQUdILEFBQ0UscUJBRG1CLEFBQ2xCLFFBQVEsQ0FBQyxFQUNSLGNBQWMsRUFBRSxHQUFHLEdBQ3BCOztBQUdILEFBQ0UscUJBRG1CLEFBQ2xCLFFBQVEsQ0FBQyxFQUNSLGNBQWMsRUFBRSxJQUFJLEdBQ3JCOztBQUlILEFBQUEsT0FBTyxDQUFDLEVBQ04sTUFBTSxFQUFFLENBQUMsQ0FBQyxDQUFDLENwQlhKLE1BQUssQ29CV2dCLENBQUMsR0FNOUI7O0FBUEQsQUFHRSxPQUhLLENBR0wsZUFBZSxDQUFDLEVBQ2QsS0FBSyxFckJsRVMsT0FBd0IsRXFCbUV0QyxVQUFVLEVwQmZMLE1BQUssR29CZ0JYOztBQ3hFSCxBQUFBLFVBQVUsQ0FBQyxFQUNULFdBQVcsRUFBRSxJQUFJLEVBQ2pCLFlBQVksRUFBRSxJQUFJLEVBQ2xCLFlBQVksRXJCcURMLE1BQUssRXFCcERaLGFBQWEsRXJCb0ROLE1BQUssRXFCbkRaLEtBQUssRUFBRSxJQUFJLEdBdUJaOztBQTVCRCxBQVNFLFVBVFEsQUFTUCxRQUFRLENBQUMsRUFDUixTQUFTLEVBQUUsTUFBNEIsR0FDeEM7O0FBWEgsQUFhRSxVQWJRLEFBYVAsUUFBUSxDQUFDLEVBQ1IsU0FBUyxFQUFFLEtBQTRCLEdBQ3hDOztBQWZILEFBaUJFLFVBakJRLEFBaUJQLFFBQVEsQ0FBQyxFQUNSLFNBQVMsRUFBRSxLQUE0QixHQUN4Qzs7QUFuQkgsQUFxQkUsVUFyQlEsQUFxQlAsUUFBUSxDQUFDLEVBQ1IsU0FBUyxFQUFFLEtBQTRCLEdBQ3hDOztBQXZCSCxBQXlCRSxVQXpCUSxBQXlCUCxRQUFRLENBQUMsRUFDUixTQUFTLEVBQUUsS0FBNEIsR0FDeEM7O0FBSUgsQUFBQSxRQUFRLEVBQ1IsUUFBUSxFQUNSLFFBQVEsRUFDUixRQUFRLEVBQ1IsUUFBUSxDQUFDLEVBQ1AsT0FBTyxFQUFFLGVBQWUsR0FDekI7O0FBR0QsQUFBQSxRQUFRLENBQUMsRUFDUCxPQUFPLEVBQUUsSUFBSSxFQUNiLFNBQVMsRUFBRSxJQUFJLEVBQ2YsV0FBVyxFckJhSixPQUFLLEVxQlpaLFlBQVksRXJCWUwsT0FBSyxHcUJHYjs7QUFuQkQsQUFNRSxRQU5NLEFBTUwsWUFBWSxDQUFDLEVBQ1osV0FBVyxFQUFFLENBQUMsRUFDZCxZQUFZLEVBQUUsQ0FBQyxHQU1oQjs7QUFkSCxBQVVJLFFBVkksQUFNTCxZQUFZLEdBSVAsT0FBTyxDQUFDLEVBQ1YsWUFBWSxFQUFFLENBQUMsRUFDZixhQUFhLEVBQUUsQ0FBQyxHQUNqQjs7QUFiTCxBQWVFLFFBZk0sQUFlTCxZQUFZLENBQUMsRUFDWixTQUFTLEVBQUUsTUFBTSxFQUNqQixVQUFVLEVBQUUsSUFBSSxHQUNqQjs7QUFFSCxBQUFBLE9BQU8sQ0FBQyxFQUNOLElBQUksRUFBRSxDQUFDLEVBQ1AsU0FBUyxFQUFFLElBQUksRUFDZixZQUFZLEVyQlBMLE1BQUssRXFCUVosYUFBYSxFckJSTixNQUFLLEdxQnlCYjs7QUFyQkQsQUFNRSxPQU5LLEFBTUosT0FBTyxFQU5WLE9BQU8sQUFPSixPQUFPLEVBUFYsT0FBTyxBQVFKLE9BQU8sRUFSVixPQUFPLEFBU0osTUFBTSxFQVRULE9BQU8sQUFVSixNQUFNLEVBVlQsT0FBTyxBQVdKLE1BQU0sRUFYVCxPQUFPLEFBWUosTUFBTSxFQVpULE9BQU8sQUFhSixNQUFNLEVBYlQsT0FBTyxBQWNKLE1BQU0sRUFkVCxPQUFPLEFBZUosTUFBTSxFQWZULE9BQU8sQUFnQkosTUFBTSxFQWhCVCxPQUFPLEFBaUJKLE1BQU0sRUFqQlQsT0FBTyxBQWtCSixTQUFTLENBQUMsRUFDVCxJQUFJLEVBQUUsSUFBSSxHQUNYOztBQUVILEFBQUEsT0FBTyxDQUFDLEVBQ04sS0FBSyxFQUFFLElBQUksR0FDWjs7QUFDRCxBQUFBLE9BQU8sQ0FBQyxFQUNOLEtBQUssRUFBRSxZQUFZLEdBQ3BCOztBQUNELEFBQUEsT0FBTyxDQUFDLEVBQ04sS0FBSyxFQUFFLFlBQVksR0FDcEI7O0FBQ0QsQUFBQSxNQUFNLENBQUMsRUFDTCxLQUFLLEVBQUUsR0FBRyxHQUNYOztBQUNELEFBQUEsTUFBTSxDQUFDLEVBQ0wsS0FBSyxFQUFFLFlBQVksR0FDcEI7O0FBQ0QsQUFBQSxNQUFNLENBQUMsRUFDTCxLQUFLLEVBQUUsWUFBWSxHQUNwQjs7QUFDRCxBQUFBLE1BQU0sQ0FBQyxFQUNMLEtBQUssRUFBRSxHQUFHLEdBQ1g7O0FBQ0QsQUFBQSxNQUFNLENBQUMsRUFDTCxLQUFLLEVBQUUsWUFBWSxHQUNwQjs7QUFDRCxBQUFBLE1BQU0sQ0FBQyxFQUNMLEtBQUssRUFBRSxZQUFZLEdBQ3BCOztBQUNELEFBQUEsTUFBTSxDQUFDLEVBQ0wsS0FBSyxFQUFFLEdBQUcsR0FDWDs7QUFDRCxBQUFBLE1BQU0sQ0FBQyxFQUNMLEtBQUssRUFBRSxZQUFZLEdBQ3BCOztBQUNELEFBQUEsTUFBTSxDQUFDLEVBQ0wsS0FBSyxFQUFFLFdBQVcsR0FDbkI7O0FBQ0QsQUFBQSxTQUFTLENBQUMsRUFDUixJQUFJLEVBQUUsUUFBUSxFQUNkLFNBQVMsRUFBRSxJQUFJLEVBQ2YsS0FBSyxFQUFFLElBQUksR0FDWjs7QUFDRCxBQUFBLFlBQVksQ0FBQyxFQUNYLFdBQVcsRUFBRSxJQUFJLEVBQ2pCLFlBQVksRUFBRSxJQUFJLEdBQ25COztBQUNELEFBQUEsWUFBWSxDQUFDLEVBQ1gsV0FBVyxFQUFFLElBQUksR0FDbEI7O0FBQ0QsQUFBQSxZQUFZLENBQUMsRUFDWCxZQUFZLEVBQUUsSUFBSSxHQUNuQjs7QUFDRCxNQUFNLEVBQUUsU0FBUyxFQUFFLE1BQU0sSUFDdkIsQUFBQSxVQUFVLEVBQ1YsVUFBVSxFQUNWLFVBQVUsRUFDVixTQUFTLEVBQ1QsU0FBUyxFQUNULFNBQVMsRUFDVCxTQUFTLEVBQ1QsU0FBUyxFQUNULFNBQVMsRUFDVCxTQUFTLEVBQ1QsU0FBUyxFQUNULFNBQVMsRUFDVCxZQUFZLENBQUMsRUFDWCxJQUFJLEVBQUUsSUFBSSxHQUNYO0VBQ0QsQUFBQSxVQUFVLENBQUMsRUFDVCxLQUFLLEVBQUUsSUFBSSxHQUNaO0VBQ0QsQUFBQSxVQUFVLENBQUMsRUFDVCxLQUFLLEVBQUUsWUFBWSxHQUNwQjtFQUNELEFBQUEsVUFBVSxDQUFDLEVBQ1QsS0FBSyxFQUFFLFlBQVksR0FDcEI7RUFDRCxBQUFBLFNBQVMsQ0FBQyxFQUNSLEtBQUssRUFBRSxHQUFHLEdBQ1g7RUFDRCxBQUFBLFNBQVMsQ0FBQyxFQUNSLEtBQUssRUFBRSxZQUFZLEdBQ3BCO0VBQ0QsQUFBQSxTQUFTLENBQUMsRUFDUixLQUFLLEVBQUUsWUFBWSxHQUNwQjtFQUNELEFBQUEsU0FBUyxDQUFDLEVBQ1IsS0FBSyxFQUFFLEdBQUcsR0FDWDtFQUNELEFBQUEsU0FBUyxDQUFDLEVBQ1IsS0FBSyxFQUFFLFlBQVksR0FDcEI7RUFDRCxBQUFBLFNBQVMsQ0FBQyxFQUNSLEtBQUssRUFBRSxZQUFZLEdBQ3BCO0VBQ0QsQUFBQSxTQUFTLENBQUMsRUFDUixLQUFLLEVBQUUsR0FBRyxHQUNYO0VBQ0QsQUFBQSxTQUFTLENBQUMsRUFDUixLQUFLLEVBQUUsWUFBWSxHQUNwQjtFQUNELEFBQUEsU0FBUyxDQUFDLEVBQ1IsS0FBSyxFQUFFLFdBQVcsR0FDbkI7RUFDRCxBQUFBLFlBQVksQ0FBQyxFQUNYLEtBQUssRUFBRSxJQUFJLEdBQ1o7RUFDRCxBQUFBLFFBQVEsQ0FBQyxFQUNQLE9BQU8sRUFBRSxlQUFlLEdBQ3pCO0VBQ0QsQUFBQSxRQUFRLENBQUMsRUFDUCxPQUFPLEVBQUUsZ0JBQWdCLEdBQzFCOztBQUVILE1BQU0sRUFBRSxTQUFTLEVBQUUsS0FBSyxJQUN0QixBQUFBLFVBQVUsRUFDVixVQUFVLEVBQ1YsVUFBVSxFQUNWLFNBQVMsRUFDVCxTQUFTLEVBQ1QsU0FBUyxFQUNULFNBQVMsRUFDVCxTQUFTLEVBQ1QsU0FBUyxFQUNULFNBQVMsRUFDVCxTQUFTLEVBQ1QsU0FBUyxFQUNULFlBQVksQ0FBQyxFQUNYLElBQUksRUFBRSxJQUFJLEdBQ1g7RUFDRCxBQUFBLFVBQVUsQ0FBQyxFQUNULEtBQUssRUFBRSxJQUFJLEdBQ1o7RUFDRCxBQUFBLFVBQVUsQ0FBQyxFQUNULEtBQUssRUFBRSxZQUFZLEdBQ3BCO0VBQ0QsQUFBQSxVQUFVLENBQUMsRUFDVCxLQUFLLEVBQUUsWUFBWSxHQUNwQjtFQUNELEFBQUEsU0FBUyxDQUFDLEVBQ1IsS0FBSyxFQUFFLEdBQUcsR0FDWDtFQUNELEFBQUEsU0FBUyxDQUFDLEVBQ1IsS0FBSyxFQUFFLFlBQVksR0FDcEI7RUFDRCxBQUFBLFNBQVMsQ0FBQyxFQUNSLEtBQUssRUFBRSxZQUFZLEdBQ3BCO0VBQ0QsQUFBQSxTQUFTLENBQUMsRUFDUixLQUFLLEVBQUUsR0FBRyxHQUNYO0VBQ0QsQUFBQSxTQUFTLENBQUMsRUFDUixLQUFLLEVBQUUsWUFBWSxHQUNwQjtFQUNELEFBQUEsU0FBUyxDQUFDLEVBQ1IsS0FBSyxFQUFFLFlBQVksR0FDcEI7RUFDRCxBQUFBLFNBQVMsQ0FBQyxFQUNSLEtBQUssRUFBRSxHQUFHLEdBQ1g7RUFDRCxBQUFBLFNBQVMsQ0FBQyxFQUNSLEtBQUssRUFBRSxZQUFZLEdBQ3BCO0VBQ0QsQUFBQSxTQUFTLENBQUMsRUFDUixLQUFLLEVBQUUsV0FBVyxHQUNuQjtFQUNELEFBQUEsWUFBWSxDQUFDLEVBQ1gsS0FBSyxFQUFFLElBQUksR0FDWjtFQUNELEFBQUEsUUFBUSxDQUFDLEVBQ1AsT0FBTyxFQUFFLGVBQWUsR0FDekI7RUFDRCxBQUFBLFFBQVEsQ0FBQyxFQUNQLE9BQU8sRUFBRSxnQkFBZ0IsR0FDMUI7O0FBRUgsTUFBTSxFQUFFLFNBQVMsRUFBRSxLQUFLLElBQ3RCLEFBQUEsVUFBVSxFQUNWLFVBQVUsRUFDVixVQUFVLEVBQ1YsU0FBUyxFQUNULFNBQVMsRUFDVCxTQUFTLEVBQ1QsU0FBUyxFQUNULFNBQVMsRUFDVCxTQUFTLEVBQ1QsU0FBUyxFQUNULFNBQVMsRUFDVCxTQUFTLEVBQ1QsWUFBWSxDQUFDLEVBQ1gsSUFBSSxFQUFFLElBQUksR0FDWDtFQUNELEFBQUEsVUFBVSxDQUFDLEVBQ1QsS0FBSyxFQUFFLElBQUksR0FDWjtFQUNELEFBQUEsVUFBVSxDQUFDLEVBQ1QsS0FBSyxFQUFFLFlBQVksR0FDcEI7RUFDRCxBQUFBLFVBQVUsQ0FBQyxFQUNULEtBQUssRUFBRSxZQUFZLEdBQ3BCO0VBQ0QsQUFBQSxTQUFTLENBQUMsRUFDUixLQUFLLEVBQUUsR0FBRyxHQUNYO0VBQ0QsQUFBQSxTQUFTLENBQUMsRUFDUixLQUFLLEVBQUUsWUFBWSxHQUNwQjtFQUNELEFBQUEsU0FBUyxDQUFDLEVBQ1IsS0FBSyxFQUFFLFlBQVksR0FDcEI7RUFDRCxBQUFBLFNBQVMsQ0FBQyxFQUNSLEtBQUssRUFBRSxHQUFHLEdBQ1g7RUFDRCxBQUFBLFNBQVMsQ0FBQyxFQUNSLEtBQUssRUFBRSxZQUFZLEdBQ3BCO0VBQ0QsQUFBQSxTQUFTLENBQUMsRUFDUixLQUFLLEVBQUUsWUFBWSxHQUNwQjtFQUNELEFBQUEsU0FBUyxDQUFDLEVBQ1IsS0FBSyxFQUFFLEdBQUcsR0FDWDtFQUNELEFBQUEsU0FBUyxDQUFDLEVBQ1IsS0FBSyxFQUFFLFlBQVksR0FDcEI7RUFDRCxBQUFBLFNBQVMsQ0FBQyxFQUNSLEtBQUssRUFBRSxXQUFXLEdBQ25CO0VBQ0QsQUFBQSxZQUFZLENBQUMsRUFDWCxLQUFLLEVBQUUsSUFBSSxHQUNaO0VBQ0QsQUFBQSxRQUFRLENBQUMsRUFDUCxPQUFPLEVBQUUsZUFBZSxHQUN6QjtFQUNELEFBQUEsUUFBUSxDQUFDLEVBQ1AsT0FBTyxFQUFFLGdCQUFnQixHQUMxQjs7QUFFSCxNQUFNLEVBQUUsU0FBUyxFQUFFLEtBQUssSUFDdEIsQUFBQSxVQUFVLEVBQ1YsVUFBVSxFQUNWLFVBQVUsRUFDVixTQUFTLEVBQ1QsU0FBUyxFQUNULFNBQVMsRUFDVCxTQUFTLEVBQ1QsU0FBUyxFQUNULFNBQVMsRUFDVCxTQUFTLEVBQ1QsU0FBUyxFQUNULFNBQVMsRUFDVCxZQUFZLENBQUMsRUFDWCxJQUFJLEVBQUUsSUFBSSxHQUNYO0VBQ0QsQUFBQSxVQUFVLENBQUMsRUFDVCxLQUFLLEVBQUUsSUFBSSxHQUNaO0VBQ0QsQUFBQSxVQUFVLENBQUMsRUFDVCxLQUFLLEVBQUUsWUFBWSxHQUNwQjtFQUNELEFBQUEsVUFBVSxDQUFDLEVBQ1QsS0FBSyxFQUFFLFlBQVksR0FDcEI7RUFDRCxBQUFBLFNBQVMsQ0FBQyxFQUNSLEtBQUssRUFBRSxHQUFHLEdBQ1g7RUFDRCxBQUFBLFNBQVMsQ0FBQyxFQUNSLEtBQUssRUFBRSxZQUFZLEdBQ3BCO0VBQ0QsQUFBQSxTQUFTLENBQUMsRUFDUixLQUFLLEVBQUUsWUFBWSxHQUNwQjtFQUNELEFBQUEsU0FBUyxDQUFDLEVBQ1IsS0FBSyxFQUFFLEdBQUcsR0FDWDtFQUNELEFBQUEsU0FBUyxDQUFDLEVBQ1IsS0FBSyxFQUFFLFlBQVksR0FDcEI7RUFDRCxBQUFBLFNBQVMsQ0FBQyxFQUNSLEtBQUssRUFBRSxZQUFZLEdBQ3BCO0VBQ0QsQUFBQSxTQUFTLENBQUMsRUFDUixLQUFLLEVBQUUsR0FBRyxHQUNYO0VBQ0QsQUFBQSxTQUFTLENBQUMsRUFDUixLQUFLLEVBQUUsWUFBWSxHQUNwQjtFQUNELEFBQUEsU0FBUyxDQUFDLEVBQ1IsS0FBSyxFQUFFLFdBQVcsR0FDbkI7RUFDRCxBQUFBLFlBQVksQ0FBQyxFQUNYLEtBQUssRUFBRSxJQUFJLEdBQ1o7RUFDRCxBQUFBLFFBQVEsQ0FBQyxFQUNQLE9BQU8sRUFBRSxlQUFlLEdBQ3pCO0VBQ0QsQUFBQSxRQUFRLENBQUMsRUFDUCxPQUFPLEVBQUUsZ0JBQWdCLEdBQzFCOztBQUVILE1BQU0sRUFBRSxTQUFTLEVBQUUsS0FBSyxJQUN0QixBQUFBLFVBQVUsRUFDVixVQUFVLEVBQ1YsVUFBVSxFQUNWLFNBQVMsRUFDVCxTQUFTLEVBQ1QsU0FBUyxFQUNULFNBQVMsRUFDVCxTQUFTLEVBQ1QsU0FBUyxFQUNULFNBQVMsRUFDVCxTQUFTLEVBQ1QsU0FBUyxFQUNULFlBQVksQ0FBQyxFQUNYLElBQUksRUFBRSxJQUFJLEdBQ1g7RUFDRCxBQUFBLFVBQVUsQ0FBQyxFQUNULEtBQUssRUFBRSxJQUFJLEdBQ1o7RUFDRCxBQUFBLFVBQVUsQ0FBQyxFQUNULEtBQUssRUFBRSxZQUFZLEdBQ3BCO0VBQ0QsQUFBQSxVQUFVLENBQUMsRUFDVCxLQUFLLEVBQUUsWUFBWSxHQUNwQjtFQUNELEFBQUEsU0FBUyxDQUFDLEVBQ1IsS0FBSyxFQUFFLEdBQUcsR0FDWDtFQUNELEFBQUEsU0FBUyxDQUFDLEVBQ1IsS0FBSyxFQUFFLFlBQVksR0FDcEI7RUFDRCxBQUFBLFNBQVMsQ0FBQyxFQUNSLEtBQUssRUFBRSxZQUFZLEdBQ3BCO0VBQ0QsQUFBQSxTQUFTLENBQUMsRUFDUixLQUFLLEVBQUUsR0FBRyxHQUNYO0VBQ0QsQUFBQSxTQUFTLENBQUMsRUFDUixLQUFLLEVBQUUsWUFBWSxHQUNwQjtFQUNELEFBQUEsU0FBUyxDQUFDLEVBQ1IsS0FBSyxFQUFFLFlBQVksR0FDcEI7RUFDRCxBQUFBLFNBQVMsQ0FBQyxFQUNSLEtBQUssRUFBRSxHQUFHLEdBQ1g7RUFDRCxBQUFBLFNBQVMsQ0FBQyxFQUNSLEtBQUssRUFBRSxZQUFZLEdBQ3BCO0VBQ0QsQUFBQSxTQUFTLENBQUMsRUFDUixLQUFLLEVBQUUsV0FBVyxHQUNuQjtFQUNELEFBQUEsWUFBWSxDQUFDLEVBQ1gsS0FBSyxFQUFFLElBQUksR0FDWjtFQUNELEFBQUEsUUFBUSxDQUFDLEVBQ1AsT0FBTyxFQUFFLGVBQWUsR0FDekI7RUFDRCxBQUFBLFFBQVEsQ0FBQyxFQUNQLE9BQU8sRUFBRSxnQkFBZ0IsR0FDMUI7O0FDemJILEFBQUEsS0FBSyxDQUFDLEVBQ0osT0FBTyxFQUFFLElBQUksRUFDYixjQUFjLEVBQUUsTUFBTSxFQUN0QixlQUFlLEVBQUUsYUFBYSxFQUM5QixjQUFjLEVBQUUsSUFBSSxFQUNwQixXQUFXLEVBQUUsSUFBSSxHQWVsQjs7QUFwQkQsQUFPRSxLQVBHLEFBT0YsUUFBUSxDQUFDLEVBQ1IsY0FBYyxFQUFFLElBQUksRUFDcEIsV0FBVyxFQUFFLElBQUksR0FDbEI7O0FBVkgsQUFZRSxLQVpHLEFBWUYsUUFBUSxDQUFDLEVBQ1IsY0FBYyxFQUFFLElBQUksRUFDcEIsV0FBVyxFQUFFLElBQUksR0FDbEI7O0FBZkgsQUFpQkUsS0FqQkcsQ0FpQkgsVUFBVSxDQUFDLEVBQ1QsT0FBTyxFdEJzQ0YsTUFBSyxHc0JyQ1g7O0FDbkJILEFBQUEsT0FBTyxDQUFDLEVBQ04sV0FBVyxFQUFFLE9BQU8sRUFDcEIsT0FBTyxFQUFFLElBQUksRUFDYixTQUFTLEVBQUUsSUFBSSxFQUNmLGVBQWUsRUFBRSxhQUFhLEdBc0IvQjs7QUExQkQsQUFNRSxPQU5LLENBTUwsZUFBZSxDQUFDLEVBQ2QsV0FBVyxFQUFFLE1BQU0sRUFDbkIsT0FBTyxFQUFFLElBQUksRUFDYixJQUFJLEVBQUUsS0FBSyxHQUtaOztBQWRILEFBV0ksT0FYRyxDQU1MLGVBQWUsQUFLWixJQUFLLENQNEpBLFlBQVksQ081SkMsV0FBVyxDQUFDLEVBQzdCLGVBQWUsRUFBRSxRQUFRLEdBQzFCOztBQWJMLEFBZ0JFLE9BaEJLLENBZ0JMLGNBQWMsQ0FBQyxFQUNiLFdBQVcsRUFBRSxNQUFNLEVBQ25CLE9BQU8sRUFBRSxJQUFJLEVBQ2IsSUFBSSxFQUFFLFFBQVEsR0FDZjs7QUFwQkgsQUFzQkUsT0F0QkssQ0FzQkwsYUFBYSxDQUFDLEVBQ1osU0FBUyxFdkJrREUsTUFBSyxFdUJqRGhCLGVBQWUsRUFBRSxJQUFJLEdBQ3RCOztBQ3pCSCxBQUlNLFVBSkksQ0FDUixLQUFLLEFBQUEsUUFBUSxHQUVULGlCQUFpQixDQUNqQixLQUFLLEVBSlgsVUFBVSxDQUVQLEFBQUEsSUFBQyxBQUFBLEVBQ0UsaUJBQWlCLENBQ2pCLEtBQUssQ0FBQyxFQUNKLFNBQVMsRUFBRSxhQUFhLEdBQ3pCOztBQU5QLEFBU0ksVUFUTSxDQUNSLEtBQUssQUFBQSxRQUFRLEdBUVQsZUFBZSxFQVRyQixVQUFVLENBRVAsQUFBQSxJQUFDLEFBQUEsRUFPRSxlQUFlLENBQUMsRUFDaEIsVUFBVSxFQUFFLEtBQUssR0FDbEI7O0FBWEwsQUFjRSxVQWRRLENBY1IsaUJBQWlCLENBQUMsRUFDaEIsT0FBTyxFQUFFLEtBQUssRUFDZCxPQUFPLEV4QnVDRixNQUFLLENBQ0wsTUFBSyxHd0JuQ1g7O0FBckJILEFBa0JJLFVBbEJNLENBY1IsaUJBQWlCLENBSWYsS0FBSyxDQUFDLEVBQ0osVUFBVSxFQUFFLGNBQWMsR0FDM0I7O0FBcEJMLEFBdUJFLFVBdkJRLENBdUJSLGVBQWUsQ0FBQyxFQUNkLGFBQWEsRXhCZ0NSLE1BQUssRXdCL0JWLFVBQVUsRUFBRSxDQUFDLEVBQ2IsUUFBUSxFQUFFLE1BQU0sRUFDaEIsVUFBVSxFQUFFLGVBQWUsR0FDNUI7O0FBSUgsQUFDRSxPQURLLEFBQUEsaUJBQWlCLEFBQ3JCLHdCQUF3QixDQUFDLEVBQ3hCLE9BQU8sRUFBRSxJQUFJLEdBQ2Q7O0FDbkNILEFBQUEsT0FBTyxDQUFDLEV2QkNOLFNBQVMsRUFBRSxNQUFTLEVBQ3BCLE1BQU0sRUY0REMsTUFBTSxFRTNEYixLQUFLLEVGMkRFLE1BQU0sRXlCNURiLFVBQVUsRTFCRkksT0FBTyxFMEJHckIsYUFBYSxFQUFFLEdBQUcsRUFDbEIsS0FBSyxFMUJGTyx5QkFBSSxFMEJHaEIsT0FBTyxFQUFFLFlBQVksRUFDckIsV0FBVyxFQUFFLEdBQUcsRUFDaEIsV0FBVyxFQUFFLElBQUksRUFDakIsTUFBTSxFQUFFLENBQUMsRUFDVCxRQUFRLEVBQUUsUUFBUSxFQUNsQixjQUFjLEVBQUUsTUFBTSxHQWlFdkI7O0FBM0VELEFBWUUsT0FaSyxBQVlKLFVBQVUsQ0FBQyxFdkJYWixTQUFTLEVBQUUsTUFBUyxFQUNwQixNQUFNLEVGd0RDLE1BQUssRUV2RFosS0FBSyxFRnVERSxNQUFLLEd5QjVDWDs7QUFkSCxBQWVFLE9BZkssQUFlSixVQUFVLENBQUMsRXZCZFosU0FBUyxFQUFFLE1BQVMsRUFDcEIsTUFBTSxFRjBEQyxNQUFNLEVFekRiLEtBQUssRUZ5REUsTUFBTSxHeUIzQ1o7O0FBakJILEFBa0JFLE9BbEJLLEFBa0JKLFVBQVUsQ0FBQyxFdkJqQlosU0FBUyxFQUFFLE1BQVMsRUFDcEIsTUFBTSxFRitERSxNQUFNLEVFOURkLEtBQUssRUY4REcsTUFBTSxHeUI3Q2I7O0FBcEJILEFBcUJFLE9BckJLLEFBcUJKLFVBQVUsQ0FBQyxFdkJwQlosU0FBUyxFQUFFLE1BQVMsRUFDcEIsTUFBTSxFRmdFRSxNQUFNLEVFL0RkLEtBQUssRUYrREcsTUFBTSxHeUIzQ2I7O0FBdkJILEFBeUJFLE9BekJLLENBeUJMLEdBQUcsQ0FBQyxFQUNGLGFBQWEsRUFBRSxHQUFHLEVBQ2xCLE1BQU0sRUFBRSxJQUFJLEVBQ1osUUFBUSxFQUFFLFFBQVEsRUFDbEIsS0FBSyxFQUFFLElBQUksRUFDWCxPQUFPLEV6QmlGQSxDQUFDLEd5QmhGVDs7QUEvQkgsQUFpQ0UsT0FqQ0ssQ0FpQ0wsWUFBWSxFQWpDZCxPQUFPLENBa0NMLGdCQUFnQixDQUFDLEVBQ2YsVUFBVSxFMUJqQ0EsSUFBSSxFMEJrQ2QsTUFBTSxFQUFFLE1BQU0sRUFDZCxNQUFNLEVBQUUsR0FBRyxFQUNYLE9BQU8sRXpCZ0JGLE1BQUssRXlCZlYsUUFBUSxFQUFFLFFBQVEsRUFDbEIsS0FBSyxFQUFFLE1BQU0sRUFDYixTQUFTLEVBQUUsbUJBQW1CLEVBQzlCLEtBQUssRUFBRSxHQUFHLEVBQ1YsT0FBTyxFekJvRUEsQ0FBQyxHeUJuRVQ7O0FBNUNILEFBOENFLE9BOUNLLENBOENMLGdCQUFnQixDQUFDLEVBQ2YsVUFBVSxFMUI1Q0QsT0FBeUIsRTBCNkNsQyxVQUFVLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEN6Qk1aLE1BQUssQ0RwREEsSUFBSSxFMEIrQ2QsYUFBYSxFQUFFLEdBQUcsRUFDbEIsTUFBTSxFQUFFLElBQUksRUFDWixLQUFLLEVBQUUsSUFBSSxHQWFaOztBQWhFSCxBQXFESSxPQXJERyxDQThDTCxnQkFBZ0IsQUFPYixPQUFPLENBQUMsRUFDUCxVQUFVLEV6QjFCQSxPQUFPLEd5QjJCbEI7O0FBdkRMLEFBeURJLE9BekRHLENBOENMLGdCQUFnQixBQVdiLEtBQUssQ0FBQyxFQUNMLFVBQVUsRXpCNUJGLE9BQU8sR3lCNkJoQjs7QUEzREwsQUE2REksT0E3REcsQ0E4Q0wsZ0JBQWdCLEFBZWIsS0FBSyxDQUFDLEVBQ0wsVUFBVSxFekJqQ0EsT0FBTyxHeUJrQ2xCOztBQS9ETCxBQWtFRSxPQWxFSyxDQWtFSixBQUFBLFlBQUMsQUFBQSxDQUFhLFFBQVEsQ0FBQyxFQUN0QixLQUFLLEVBQUUsWUFBWSxFQUNuQixPQUFPLEVBQUUsa0JBQWtCLEVBQzNCLElBQUksRUFBRSxHQUFHLEVBQ1QsUUFBUSxFQUFFLFFBQVEsRUFDbEIsR0FBRyxFQUFFLEdBQUcsRUFDUixTQUFTLEVBQUUscUJBQXFCLEVBQ2hDLE9BQU8sRXpCc0NBLENBQUMsR3lCckNUOztBQzFFSCxBQUFBLE1BQU0sQ0FBQyxFQUNMLFFBQVEsRUFBRSxRQUFRLEVBQ2xCLFdBQVcsRUFBRSxNQUFNLEdBd0RwQjs7QUExREQsQUFNSSxNQU5FLENBSUgsQUFBQSxVQUFDLEFBQUEsQ0FFQyxPQUFPLEVBTlosTUFBTSxBQUtILElBQUssRUFBQSxBQUFBLFVBQUMsQUFBQSxFQUNKLE9BQU8sQ0FBQyxFQUNQLFVBQVUsRTNCUEEsT0FBTyxFMkJRakIsZUFBZSxFQUFFLFdBQVcsRUFDNUIsYUFBYSxFQUFFLEtBQUssRUFDcEIsVUFBVSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQUssQzNCUmYsSUFBSSxFMkJTWixLQUFLLEUzQlRHLElBQUksRTJCVVosT0FBTyxFQUFFLGdCQUFnQixFQUN6QixPQUFPLEVBQUUsWUFBWSxFQUNyQixTQUFTLEVBQUUsNEJBQTBCLEdBQ3RDOztBQWZMLEFBa0JJLE1BbEJFLENBaUJILEFBQUEsVUFBQyxBQUFBLENBQ0MsT0FBTyxDQUFDLEVBQ1AsU0FBUyxFMUJxREEsTUFBSyxFMEJwRGQsTUFBTSxFQUFFLEtBQUssRUFDYixXQUFXLEVBQUUsQ0FBQyxFQUNkLFNBQVMsRUFBRSxLQUFLLEVBQ2hCLE9BQU8sRUFBRSxXQUFXLEVBQ3BCLFVBQVUsRUFBRSxNQUFNLEVBQ2xCLFdBQVcsRUFBRSxNQUFNLEdBQ3BCOztBQTFCTCxBQThCSSxNQTlCRSxBQTRCSCxJQUFLLEVBdkJBLEFBQUEsVUFBQyxBQUFBLEVBeUJKLE9BQU8sRUE5QlosTUFBTSxDQTZCSCxBQUFBLFVBQUMsQ0FBVyxFQUFFLEFBQWIsQ0FDQyxPQUFPLENBQUMsRUFDUCxNQUFNLEVBQUUsR0FBRyxFQUNYLFNBQVMsRUFBRSxHQUFHLEVBQ2QsT0FBTyxFQUFFLENBQUMsRUFDVixLQUFLLEVBQUUsR0FBRyxHQUNYOztBQW5DTCxBQXdDSSxNQXhDRSxBQXVDSCxJQUFJLEFBQ0YsT0FBTyxFQXhDWixNQUFNLEE0QkdOLE9BQU8sQTVCcUNGLE9BQU8sQ0FBQyxFQUNQLFFBQVEsRUFBRSxRQUFRLEVBQ2xCLEdBQUcsRUFBRSxDQUFDLEVBQ04sS0FBSyxFQUFFLENBQUMsRUFDUixTQUFTLEVBQUUsb0JBQW9CLEdBQ2hDOztBQTdDTCxBQWtESSxNQWxERSxBQWlESCxPQUFPLEFBQ0wsT0FBTyxDQUFDLEVBQ1AsUUFBUSxFQUFFLFFBQVEsRUFDbEIsR0FBRyxFQUFFLE1BQU0sRUFDWCxLQUFLLEVBQUUsTUFBTSxFQUNiLFNBQVMsRUFBRSxvQkFBb0IsRUFDL0IsT0FBTyxFMUJ5REYsR0FBRyxHMEJ4RFQ7O0FDeERMLEFBQUEsV0FBVyxDQUFDLEVBQ1YsVUFBVSxFQUFFLElBQUksRUFDaEIsTUFBTSxFM0JxREMsTUFBSyxDMkJyREksQ0FBQyxFQUNqQixPQUFPLEUzQm9EQSxNQUFLLEMyQnBESyxDQUFDLEdBd0JuQjs7QUEzQkQsQUFLRSxXQUxTLENBS1QsZ0JBQWdCLENBQUMsRUFDZixLQUFLLEU1QkZTLE9BQXdCLEU0Qkd0QyxPQUFPLEVBQUUsWUFBWSxFQUNyQixNQUFNLEVBQUUsQ0FBQyxFQUNULE9BQU8sRTNCOENGLE1BQUssQzJCOUNPLENBQUMsR0FpQm5COztBQTFCSCxBQVdJLFdBWE8sQ0FLVCxnQkFBZ0IsQUFNYixJQUFLLENYd0pZLFdBQVcsRVd4SlYsRUFDakIsWUFBWSxFM0IyQ1QsTUFBSyxHMkJ0Q1Q7O0FBakJMLEFBY00sV0FkSyxDQUtULGdCQUFnQixBQU1iLElBQUssQ1h3SlksV0FBVyxFV3JKM0IsQ0FBQyxDQUFDLEVBQ0EsS0FBSyxFNUJYSyxPQUF3QixHNEJZbkM7O0FBaEJQLEFBb0JNLFdBcEJLLENBS1QsZ0JBQWdCLEFBY2IsSUFBSyxDWG9KQSxZQUFZLENXbkpmLFFBQVEsQ0FBQyxFQUNSLEtBQUssRTVCakJLLE9BQXdCLEU0QmtCbEMsT0FBTyxFQUFFLEdBQUcsRUFDWixhQUFhLEUzQmlDWixNQUFLLEcyQmhDUDs7QUN4QlAsQUFBQSxJQUFJLENBQUMsRUFDSCxVQUFVLEU1QnVCSSxPQUFxQixFNEJ0Qm5DLGFBQWEsRTVCb0ROLE1BQUssRTRCbkRaLE9BQU8sRUFBRSxJQUFJLEVBQ2IsU0FBUyxFQUFFLE1BQU0sRUFDakIsTUFBTSxFNUJxREMsTUFBSyxFNEJwRFosS0FBSyxFQUFFLElBQUksR0E2Qlo7O0FBbkNELEFBUUUsSUFSRSxBQVFELE9BQU8sQ0FBQyxFQUNQLE1BQU0sRTVCOENELE1BQUssRzRCN0NYOztBQVZILEFBYUUsSUFiRSxDQWFGLFNBQVMsQ0FBQyxFQUNSLFVBQVUsRTdCZEUsT0FBTyxFNkJlbkIsS0FBSyxFN0JiSyxJQUFJLEU2QmNkLE9BQU8sRUFBRSxLQUFLLEVBQ2QsU0FBUyxFNUJ1REUsTUFBSyxFNEJ0RGhCLFdBQVcsRUFBRSxDQUFDLEVBQ2QsV0FBVyxFNUJ1Q04sTUFBSyxFNEJ0Q1YsTUFBTSxFQUFFLElBQUksRUFDWixRQUFRLEVBQUUsUUFBUSxFQUNsQixVQUFVLEVBQUUsTUFBTSxFQUNsQixLQUFLLEVBQUUsQ0FBQyxHQVdUOztBQWxDSCxBQXlCSSxJQXpCQSxDQWFGLFNBQVMsQUFZTixZQUFZLENBQUMsRUFDWix5QkFBeUIsRTVCNEJ0QixNQUFLLEU0QjNCUixzQkFBc0IsRTVCMkJuQixNQUFLLEc0QjFCVDs7QUE1QkwsQUE2QkksSUE3QkEsQ0FhRixTQUFTLEFBZ0JOLFdBQVcsQ0FBQyxFQUNYLDBCQUEwQixFNUJ3QnZCLE1BQUssRTRCdkJSLHVCQUF1QixFNUJ1QnBCLE1BQUssRTRCdEJSLFdBQVcsRUFBRSxDQUFDLEdBQ2Y7O0FBS0wsQUFBQSxXQUFXLENBQUMsRUFDVixNQUFNLEU1QmVDLE1BQUssRTRCZFosTUFBTSxFNUJnQkMsTUFBSyxDNEJoQlksQ0FBQyxFQUN6QixRQUFRLEVBQUUsUUFBUSxHQTRCbkI7O0FBL0JELEFBS0UsV0FMUyxDQUtULFNBQVMsQ0FBQyxFQUNSLElBQUksRUFBRSxDQUFDLEVBQ1AsT0FBTyxFQUFFLENBQUMsRUFDVixRQUFRLEVBQUUsUUFBUSxHQUtuQjs7QUFiSCxBQVNJLFdBVE8sQ0FLVCxTQUFTLEFBSU4sSUFBSyxDWm9IWSxXQUFXLENZcEhYLFlBQVksQ0FBQyxFQUM3QixVQUFVLEU1QnhCQSxPQUFxQixFNEJ5Qi9CLE9BQU8sRTVCOERGLENBQUMsRzRCN0RQOztBQVpMLEFBZUUsV0FmUyxDQWVULGVBQWUsQ0FBQyxFQUNkLFVBQVUsRTdCdERFLE9BQU8sRTZCdURuQixNQUFNLEVBQUUsQ0FBQyxFQUNULGFBQWEsRUFBRSxHQUFHLEVBQ2xCLE1BQU0sRTVCQUQsTUFBSyxFNEJDVixPQUFPLEVBQUUsQ0FBQyxFQUNWLFFBQVEsRUFBRSxRQUFRLEVBQ2xCLEtBQUssRUFBRSxDQUFDLEVBQ1IsR0FBRyxFQUFFLEdBQUcsRUFDUixTQUFTLEVBQUUsb0JBQW9CLEVBQy9CLEtBQUssRTVCTkEsTUFBSyxHNEJXWDs7QUE5QkgsQUEyQkksV0EzQk8sQ0FlVCxlQUFlLEFBWVosT0FBTyxDQUFDLEVBQ1AsVUFBVSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQUssQzdCbEViLE9BQU8sRzZCbUVsQjs7QUNuRUwsQUFBQSxLQUFLLENBQUMsRUFDSixVQUFVLEU5QkNFLElBQUksRThCQWhCLE1BQU0sRTdCbURDLE9BQU0sQzZCbkRTLEtBQUssQzlCR2QsT0FBeUIsRThCRnRDLGFBQWEsRTdCbUROLE1BQUssRTZCbERaLE9BQU8sRUFBRSxJQUFJLEVBQ2IsY0FBYyxFQUFFLE1BQU0sR0FvQ3ZCOztBQXpDRCxBQU9FLEtBUEcsQ0FPSCxZQUFZLEVBUGQsS0FBSyxDQVFILFVBQVUsRUFSWixLQUFLLENBU0gsWUFBWSxDQUFDLEVBQ1gsT0FBTyxFN0JnREYsTUFBSyxFNkIvQ1YsY0FBYyxFQUFFLENBQUMsR0FLbEI7O0FBaEJILEFBYUksS0FiQyxDQU9ILFlBQVksQUFNVCxXQUFXLEVBYmhCLEtBQUssQ0FRSCxVQUFVLEFBS1AsV0FBVyxFQWJoQixLQUFLLENBU0gsWUFBWSxBQUlULFdBQVcsQ0FBQyxFQUNYLGNBQWMsRTdCNENYLE1BQUssRzZCM0NUOztBQWZMLEFBa0JFLEtBbEJHLENBa0JILFVBQVUsQ0FBQyxFQUNULElBQUksRUFBRSxRQUFRLEdBQ2Y7O0FBcEJILEFBc0JFLEtBdEJHLENBc0JILFdBQVcsQ0FBQyxFQUNWLFdBQVcsRTdCbUNOLE1BQUssRzZCbEJYOztBQXhDSCxBQXlCSSxLQXpCQyxDQXNCSCxXQUFXLEFBR1IsWUFBWSxDQUFDLEVBQ1osV0FBVyxFQUFFLENBQUMsR0FNZjs7QUFoQ0wsQUE0Qk0sS0E1QkQsQ0FzQkgsV0FBVyxBQUdSLFlBQVksQ0FHWCxHQUFHLENBQUMsRUFDRixzQkFBc0IsRTdCeUJyQixNQUFLLEU2QnhCTix1QkFBdUIsRTdCd0J0QixNQUFLLEc2QnZCUDs7QUEvQlAsQUFtQ00sS0FuQ0QsQ0FzQkgsV0FBVyxBQVlSLFdBQVcsQ0FDVixHQUFHLENBQUMsRUFDRix5QkFBeUIsRTdCa0J4QixNQUFLLEU2QmpCTiwwQkFBMEIsRTdCaUJ6QixNQUFLLEc2QmhCUDs7QUN0Q1AsQUFBQSxLQUFLLENBQUMsRUFDSixXQUFXLEVBQUUsTUFBTSxFQUNuQixVQUFVLEU5QnNCSSxPQUFxQixFOEJyQm5DLGFBQWEsRUFBRSxJQUFJLEVBQ25CLE9BQU8sRUFBRSxXQUFXLEVBQ3BCLFNBQVMsRUFBRSxHQUFHLEVBQ2QsTUFBTSxFOUJzREMsTUFBTSxFOEJyRGIsV0FBVyxFOUJtREosTUFBSyxFOEJsRFosTUFBTSxFOUI4Q0MsTUFBSyxFOEI3Q1osU0FBUyxFOUJzRlEsS0FBSyxFOEJyRnRCLFFBQVEsRUFBRSxNQUFNLEVBQ2hCLE9BQU8sRTlCNENBLE1BQUssQ0FDTCxNQUFLLEU4QjVDWixlQUFlLEVBQUUsSUFBSSxFQUNyQixhQUFhLEVBQUUsUUFBUSxFQUN2QixjQUFjLEVBQUUsTUFBTSxFQUN0QixXQUFXLEVBQUUsTUFBTSxHQWdCcEI7O0FBL0JELEFBaUJFLEtBakJHLEFBaUJGLE9BQU8sQ0FBQyxFQUNQLFVBQVUsRS9CbEJFLE9BQU8sRStCbUJuQixLQUFLLEUvQmpCSyxJQUFJLEcrQmtCZjs7QUFwQkgsQUFzQkUsS0F0QkcsQ0FzQkgsT0FBTyxDQUFDLEVBQ04sV0FBVyxFOUJpQ04sT0FBSyxFOEJoQ1YsWUFBWSxFOUIrQlAsTUFBSyxHOEI5Qlg7O0FBekJILEFBMkJFLEtBM0JHLENBMkJILFVBQVUsQ0FBQyxFQUNULGFBQWEsRUFBRSxHQUFHLEVBQ2xCLFNBQVMsRUFBRSxXQUFVLEdBQ3RCOztBQzlCSCxBQUFBLFNBQVMsQ0FBQyxFQUNSLE9BQU8sRUFBRSxZQUFZLEVBQ3JCLFFBQVEsRUFBRSxRQUFRLEdBZ0NuQjs7QUFsQ0QsQUFJRSxTQUpPLENBSVAsS0FBSyxDQUFDLEVBQ0osU0FBUyxFQUFFLHNCQUFzQixFQUNqQyxPQUFPLEVBQUUsSUFBSSxFQUNiLElBQUksRUFBRSxDQUFDLEVBQ1AsVUFBVSxFQUFFLElBQUksRUFDaEIsVUFBVSxFQUFFLElBQUksRUFDaEIsUUFBUSxFQUFFLFFBQVEsRUFDbEIsR0FBRyxFQUFFLElBQUksR0FDVjs7QUFaSCxBQWVJLFNBZkssQUFjTixlQUFlLENBQ2QsS0FBSyxDQUFDLEVBQ0osSUFBSSxFQUFFLElBQUksRUFDVixLQUFLLEVBQUUsQ0FBQyxHQUNUOztBQWxCTCxBQXFCRSxTQXJCTyxBQXFCTixPQUFPLENBQUMsS0FBSyxFQXJCaEIsU0FBUyxDQXNCUCxnQkFBZ0IsQUFBQSxNQUFNLEdBQUcsS0FBSyxFQXRCaEMsU0FBUyxDQXVCUCxLQUFLLEFBQUEsTUFBTSxDQUFDLEVBQ1YsT0FBTyxFQUFFLEtBQUssR0FDZjs7QUF6QkgsQUE2QkksU0E3QkssQ0E0QlAsVUFBVSxDQUNSLGdCQUFnQixBQUFBLGVBQWdCLENBQUEsQ0FBQyxFQUFFLEVBQ2pDLDBCQUEwQixFL0J3QnZCLE1BQUssRStCdkJSLHVCQUF1QixFL0J1QnBCLE1BQUssRytCdEJUOztBQ2hDTCxBQUFBLE1BQU0sQ0FBQyxFQUNMLFVBQVUsRWpDS0QsT0FBeUIsRWlDSmxDLGFBQWEsRWhDb0ROLE1BQUssRWdDbkRaLEtBQUssRWpDQ1csT0FBd0IsRWlDQXhDLFVBQVUsRUFBRSxNQUFNLEVBQ2xCLE9BQU8sRWhDNkRDLE1BQU0sQ0FKUCxNQUFNLEdnQzNDZDs7QUFuQkQsQUFPRSxNQVBJLENBT0osV0FBVyxDQUFDLEVBQ1YsYUFBYSxFaENrRFIsTUFBSyxHZ0NqRFg7O0FBVEgsQUFXRSxNQVhJLENBV0osWUFBWSxFQVhkLE1BQU0sQ0FZSixlQUFlLENBQUMsRUFDZCxNQUFNLEVoQzJDRCxNQUFLLENnQzNDYyxJQUFJLEdBQzdCOztBQWRILEFBZ0JFLE1BaEJJLENBZ0JKLGFBQWEsQ0FBQyxFQUNaLFVBQVUsRWhDeUNMLE1BQUssR2dDeENYOztBQ2xCSCxBQUFBLEtBQUssQ0FBQyxFekJNSixVQUFVLEVBQUUsQ0FBQyxDeUJMVyxPQUFNLEN6QktSLE1BQXNCLENUTGpDLHFCQUFPLEVrQ0NsQixVQUFVLEVsQ0FFLElBQUksRWtDQ2hCLGFBQWEsRWpDbUROLE1BQUssRWlDbERaLFVBQVUsRUFBRSxJQUFJLEVBQ2hCLE1BQU0sRUFBRSxDQUFDLEVBQ1QsU0FBUyxFakN3RlEsS0FBSyxFaUN2RnRCLE9BQU8sRWpDaURBLE1BQUssRWlDaERaLFNBQVMsRUFBRSxrQkFBOEIsRUFDekMsT0FBTyxFakN5R0UsR0FBRyxHaUNsRGI7O0FBaEVELEFBV0UsS0FYRyxBQVdGLFNBQVMsQ0FBQyxFQUNULFVBQVUsRUFBRSxXQUFXLEVBQ3ZCLFVBQVUsRUFBRSxJQUFJLEdBQ2pCOztBQWRILEFBZ0JFLEtBaEJHLENBZ0JILFVBQVUsQ0FBQyxFQUNULFVBQVUsRUFBRSxDQUFDLEVBQ2IsT0FBTyxFQUFFLENBQUMsQ2pDc0NMLE1BQUssRWlDckNWLFFBQVEsRUFBRSxRQUFRLEVBQ2xCLGVBQWUsRUFBRSxJQUFJLEdBOEJ0Qjs7QUFsREgsQUFzQkksS0F0QkMsQ0FnQkgsVUFBVSxHQU1KLENBQUMsQ0FBQyxFQUNKLGFBQWEsRWpDK0JWLE1BQUssRWlDOUJSLEtBQUssRUFBRSxPQUFPLEVBQ2QsT0FBTyxFQUFFLEtBQUssRUFDZCxNQUFNLEVBQUUsQ0FBQyxDakM4Qk4sT0FBSyxFaUM3QlIsT0FBTyxFakM0QkosTUFBSyxDQUNMLE1BQUssRWlDNUJSLGVBQWUsRUFBRSxJQUFJLEdBV3RCOztBQXZDTCxBQTZCTSxLQTdCRCxDQWdCSCxVQUFVLEdBTUosQ0FBQyxBQU9GLE1BQU0sRUE3QmIsS0FBSyxDQWdCSCxVQUFVLEdBTUosQ0FBQyxBQVFGLE1BQU0sQ0FBQyxFQUNOLFVBQVUsRWpDdEJBLE9BQThCLEVpQ3VCeEMsS0FBSyxFbENoQ0csT0FBTyxHa0NpQ2hCOztBQWpDUCxBQWtDTSxLQWxDRCxDQWdCSCxVQUFVLEdBTUosQ0FBQyxBQVlGLE9BQU8sRUFsQ2QsS0FBSyxDQWdCSCxVQUFVLEdBTUosQ0FBQyxBQWFGLE9BQU8sQ0FBQyxFQUNQLFVBQVUsRWpDM0JBLE9BQThCLEVpQzRCeEMsS0FBSyxFbENyQ0csT0FBTyxHa0NzQ2hCOztBQXRDUCxBQXlDSSxLQXpDQyxDQWdCSCxVQUFVLENBeUJSLGNBQWMsRUF6Q2xCLEtBQUssQ0FnQkgsVUFBVSxDQTBCUixXQUFXLEVBMUNmLEtBQUssQ0FnQkgsVUFBVSxDQTJCUixZQUFZLENBQUMsRUFDWCxNQUFNLEVqQ1VILE1BQUssQ2lDVlEsQ0FBQyxHQUNsQjs7QUE3Q0wsQUErQ0ksS0EvQ0MsQ0FnQkgsVUFBVSxHQStCSixVQUFVLENBQUMsRUFDYixVQUFVLEVqQ09QLE1BQUssR2lDTlQ7O0FBakRMLEFBb0RFLEtBcERHLENBb0RILFdBQVcsQ0FBQyxFQUNWLFdBQVcsRUFBRSxNQUFNLEVBQ25CLE9BQU8sRUFBRSxJQUFJLEVBQ2IsTUFBTSxFQUFFLElBQUksRUFDWixRQUFRLEVBQUUsUUFBUSxFQUNsQixLQUFLLEVBQUUsQ0FBQyxFQUNSLEdBQUcsRUFBRSxDQUFDLEdBS1A7O0FBL0RILEFBNERJLEtBNURDLENBb0RILFdBQVcsQ0FRVCxNQUFNLENBQUMsRUFDTCxZQUFZLEVqQ0xULE1BQUssR2lDTVQ7O0FDOURMLEFBQUEsTUFBTSxDQUFDLEVBQ0wsV0FBVyxFQUFFLE1BQU0sRUFDbkIsTUFBTSxFQUFFLENBQUMsRUFDVCxPQUFPLEVBQUUsSUFBSSxFQUNiLGVBQWUsRUFBRSxNQUFNLEVBQ3ZCLElBQUksRUFBRSxDQUFDLEVBQ1AsT0FBTyxFQUFFLENBQUMsRUFDVixRQUFRLEVBQUUsTUFBTSxFQUNoQixPQUFPLEVsQ2dEQSxNQUFLLEVrQy9DWixRQUFRLEVBQUUsS0FBSyxFQUNmLEtBQUssRUFBRSxDQUFDLEVBQ1IsR0FBRyxFQUFFLENBQUMsR0EwQ1A7O0FBckRELEFBYUUsTUFiSSxBQWFILE9BQU8sRUFiVixNQUFNLEFBY0gsT0FBTyxDQUFDLEVBQ1AsT0FBTyxFQUFFLElBQUksRUFDYixPQUFPLEVBQUUsQ0FBQyxFQUNWLE9BQU8sRWxDa0dBLEdBQUcsR2tDakZYOztBQWxDSCxBQW1CSSxNQW5CRSxBQWFILE9BQU8sQ0FNTixjQUFjLEVBbkJsQixNQUFNLEFBY0gsT0FBTyxDQUtOLGNBQWMsQ0FBQyxFQUNiLFVBQVUsRW5DZEwseUJBQXlCLEVtQ2U5QixNQUFNLEVBQUUsQ0FBQyxFQUNULE1BQU0sRUFBRSxPQUFPLEVBQ2YsT0FBTyxFQUFFLEtBQUssRUFDZCxJQUFJLEVBQUUsQ0FBQyxFQUNQLFFBQVEsRUFBRSxRQUFRLEVBQ2xCLEtBQUssRUFBRSxDQUFDLEVBQ1IsR0FBRyxFQUFFLENBQUMsR0FDUDs7QUE1QkwsQUE4QkksTUE5QkUsQUFhSCxPQUFPLENBaUJOLGdCQUFnQixFQTlCcEIsTUFBTSxBQWNILE9BQU8sQ0FnQk4sZ0JBQWdCLENBQUMsRUFDZixTQUFTLEVBQUUscUJBQXFCLEVBQ2hDLE9BQU8sRWxDK0VGLENBQUMsR2tDOUVQOztBQWpDTCxBQXFDSSxNQXJDRSxBQW9DSCxTQUFTLENBQ1IsZ0JBQWdCLENBQUMsRUFDZixTQUFTLEVsQ3lESSxLQUFLLEVrQ3hEbEIsT0FBTyxFQUFFLENBQUMsQ2xDaUJQLE1BQUssR2tDaEJUOztBQXhDTCxBQTRDSSxNQTVDRSxBQTJDSCxTQUFTLENBQ1IsY0FBYyxDQUFDLEVBQ2IsVUFBVSxFbkMzQ0YsSUFBSSxHbUM0Q2I7O0FBOUNMLEFBZ0RJLE1BaERFLEFBMkNILFNBQVMsQ0FLUixnQkFBZ0IsQ0FBQyxFQUNmLFVBQVUsRUFBRSxJQUFJLEVBQ2hCLFNBQVMsRWxDK0NJLEtBQUssR2tDOUNuQjs7QUFJTCxBQUFBLGdCQUFnQixDQUFDLEUxQmpEZixVQUFVLEVBQUUsQ0FBQyxDMEJrRFcsTUFBSyxDMUJsRFAsTUFBc0IsQ1RMakMscUJBQU8sRW1Dd0RsQixVQUFVLEVuQ3ZERSxJQUFJLEVtQ3dEaEIsYUFBYSxFbENKTixNQUFLLEVrQ0taLE9BQU8sRUFBRSxJQUFJLEVBQ2IsY0FBYyxFQUFFLE1BQU0sRUFDdEIsVUFBVSxFQUFFLElBQUksRUFDaEIsU0FBUyxFbENrQ1EsS0FBSyxFa0NqQ3RCLE9BQU8sRUFBRSxDQUFDLENsQ0xILE1BQUssRWtDTVosS0FBSyxFQUFFLElBQUksR0FxQlo7O0FBOUJELEFBV0UsZ0JBWGMsQUFXYixpQkFBaUIsQ0FBQyxFQUNqQixVQUFVLEVBQUUsS0FBSyxHQUNsQjs7QUFiSCxBQWVFLGdCQWZjLENBZWQsYUFBYSxDQUFDLEVBQ1osS0FBSyxFbkN0RUksT0FBTyxFbUN1RWhCLE9BQU8sRWxDZEYsTUFBSyxHa0NlWDs7QUFsQkgsQUFvQkUsZ0JBcEJjLENBb0JkLFdBQVcsQ0FBQyxFQUNWLFVBQVUsRUFBRSxJQUFJLEVBQ2hCLE9BQU8sRWxDbkJGLE1BQUssRWtDb0JWLFFBQVEsRUFBRSxRQUFRLEdBQ25COztBQXhCSCxBQTBCRSxnQkExQmMsQ0EwQmQsYUFBYSxDQUFDLEVBQ1osT0FBTyxFbEN4QkYsTUFBSyxFa0N5QlYsVUFBVSxFQUFFLEtBQUssR0FDbEI7O0FDcEZILEFBQUEsSUFBSSxDQUFDLEVBQ0gsT0FBTyxFQUFFLElBQUksRUFDYixjQUFjLEVBQUUsTUFBTSxFQUN0QixVQUFVLEVBQUUsSUFBSSxFQUNoQixNQUFNLEVuQ21EQyxNQUFLLENtQ25ESSxDQUFDLEdBNEJsQjs7QUFoQ0QsQUFPSSxJQVBBLENBTUYsU0FBUyxDQUNQLENBQUMsQ0FBQyxFQUNBLEtBQUssRXBDSk8sT0FBd0IsRW9DS3BDLE9BQU8sRW5DOENKLE1BQUssQ0FDTCxNQUFLLEVtQzlDUixlQUFlLEVBQUUsSUFBSSxHQUt0Qjs7QUFmTCxBQVdNLElBWEYsQ0FNRixTQUFTLENBQ1AsQ0FBQyxBQUlFLE1BQU0sRUFYYixJQUFJLENBTUYsU0FBUyxDQUNQLENBQUMsQUFLRSxNQUFNLENBQUMsRUFDTixLQUFLLEVwQ2JHLE9BQU8sR29DY2hCOztBQWRQLEFBaUJNLElBakJGLENBTUYsU0FBUyxBQVVOLE9BQU8sR0FDRixDQUFDLENBQUMsRUFDSixLQUFLLEVBQUUsT0FBNkIsRUFDcEMsV0FBVyxFQUFFLElBQUksR0FLbEI7O0FBeEJQLEFBb0JRLElBcEJKLENBTUYsU0FBUyxBQVVOLE9BQU8sR0FDRixDQUFDLEFBR0YsTUFBTSxFQXBCZixJQUFJLENBTUYsU0FBUyxBQVVOLE9BQU8sR0FDRixDQUFDLEFBSUYsTUFBTSxDQUFDLEVBQ04sS0FBSyxFcEN0QkMsT0FBTyxHb0N1QmQ7O0FBdkJULEFBNEJFLElBNUJFLENBNEJBLElBQUksQ0FBQyxFQUNMLGFBQWEsRW5DMkJSLE1BQUssRW1DMUJWLFdBQVcsRW5DNEJOLE1BQUssR21DM0JYOztBQy9CSCxBQUFBLFdBQVcsQ0FBQyxFQUNWLE9BQU8sRUFBRSxJQUFJLEVBQ2IsVUFBVSxFQUFFLElBQUksRUFDaEIsTUFBTSxFcENvREMsTUFBSyxDb0NwREksQ0FBQyxFQUNqQixPQUFPLEVwQ21EQSxNQUFLLENvQ25ESyxDQUFDLEdBc0RuQjs7QUExREQsQUFNRSxXQU5TLENBTVQsVUFBVSxDQUFDLEVBQ1QsTUFBTSxFcENnREQsTUFBSyxDQUZMLE9BQU0sR29DSVo7O0FBekRILEFBU0ksV0FUTyxDQU1ULFVBQVUsQ0FHUixJQUFJLENBQUMsRUFDSCxPQUFPLEVBQUUsWUFBWSxFQUNyQixPQUFPLEVwQzRDSixNQUFLLENBQUwsTUFBSyxHb0MzQ1Q7O0FBWkwsQUFjSSxXQWRPLENBTVQsVUFBVSxDQVFSLENBQUMsQ0FBQyxFQUNBLGFBQWEsRXBDdUNWLE1BQUssRW9DdENSLE9BQU8sRUFBRSxZQUFZLEVBQ3JCLE9BQU8sRXBDc0NKLE1BQUssQ0FDTCxNQUFLLEVvQ3RDUixlQUFlLEVBQUUsSUFBSSxHQUt0Qjs7QUF2QkwsQUFtQk0sV0FuQkssQ0FNVCxVQUFVLENBUVIsQ0FBQyxBQUtFLE1BQU0sRUFuQmIsV0FBVyxDQU1ULFVBQVUsQ0FRUixDQUFDLEFBTUUsTUFBTSxDQUFDLEVBQ04sS0FBSyxFckNyQkcsT0FBTyxHcUNzQmhCOztBQXRCUCxBQTBCTSxXQTFCSyxDQU1ULFVBQVUsQUFtQlAsU0FBUyxDQUNSLENBQUMsQ0FBQyxFQUNBLE1BQU0sRUFBRSxPQUFPLEVBQ2YsT0FBTyxFQUFFLEVBQUUsRUFDWCxjQUFjLEVBQUUsSUFBSSxHQUNyQjs7QUE5QlAsQUFrQ00sV0FsQ0ssQ0FNVCxVQUFVLEFBMkJQLE9BQU8sQ0FDTixDQUFDLENBQUMsRUFDQSxVQUFVLEVyQ25DRixPQUFPLEVxQ29DZixLQUFLLEVyQ2xDQyxJQUFJLEdxQ21DWDs7QUFyQ1AsQUF3Q0ksV0F4Q08sQ0FNVCxVQUFVLEFBa0NQLFVBQVUsRUF4Q2YsV0FBVyxDQU1ULFVBQVUsQUFtQ1AsVUFBVSxDQUFDLEVBQ1YsSUFBSSxFQUFFLE9BQU8sR0FDZDs7QUEzQ0wsQUE2Q0ksV0E3Q08sQ0FNVCxVQUFVLEFBdUNQLFVBQVUsQ0FBQyxFQUNWLFVBQVUsRUFBRSxLQUFLLEdBQ2xCOztBQS9DTCxBQWlESSxXQWpETyxDQU1ULFVBQVUsQ0EyQ1IsZ0JBQWdCLENBQUMsRUFDZixNQUFNLEVBQUUsQ0FBQyxHQUNWOztBQW5ETCxBQXFESSxXQXJETyxDQU1ULFVBQVUsQ0ErQ1IsbUJBQW1CLENBQUMsRUFDbEIsTUFBTSxFQUFFLENBQUMsRUFDVCxPQUFPLEVBQUUsRUFBRSxHQUNaOztBQ3hETCxBQUFBLE1BQU0sQ0FBQyxFQUNMLE1BQU0sRXJDb0RDLE9BQU0sQ3FDcERTLEtBQUssQ3RDSWQsT0FBeUIsRXNDSHRDLGFBQWEsRXJDb0ROLE1BQUssRXFDbkRaLE9BQU8sRUFBRSxJQUFJLEVBQ2IsY0FBYyxFQUFFLE1BQU0sR0FpQnZCOztBQXJCRCxBQU1FLE1BTkksQ0FNSixhQUFhLEVBTmYsTUFBTSxDQU9KLGFBQWEsQ0FBQyxFQUNaLElBQUksRUFBRSxRQUFRLEVBQ2QsT0FBTyxFckNpREYsTUFBSyxHcUNoRFg7O0FBVkgsQUFZRSxNQVpJLENBWUosVUFBVSxDQUFDLEVBQ1QsSUFBSSxFQUFFLFFBQVEsR0FDZjs7QUFkSCxBQWdCRSxNQWhCSSxDQWdCSixXQUFXLENBQUMsRUFDVixJQUFJLEVBQUUsUUFBUSxFQUNkLFVBQVUsRUFBRSxJQUFJLEVBQ2hCLE9BQU8sRUFBRSxDQUFDLENyQ3VDTCxNQUFLLEdxQ3RDWDs7QUNwQkgsQUFBQSxRQUFRLENBQUMsRUFDUCxPQUFPLEVBQUUsWUFBWSxFQUNyQixRQUFRLEVBQUUsUUFBUSxHQTZEbkI7O0FBL0RELEFBSUUsUUFKTSxDQUlOLGtCQUFrQixDQUFDLEVBQ2pCLElBQUksRUFBRSxHQUFHLEVBQ1QsT0FBTyxFQUFFLENBQUMsRUFDVixPQUFPLEV0Q2lERixNQUFLLEVzQ2hEVixRQUFRLEVBQUUsUUFBUSxFQUNsQixHQUFHLEVBQUUsQ0FBQyxFQUNOLFNBQVMsRUFBRSxxQkFBcUIsQ0FBQyxRQUFRLEVBQ3pDLFVBQVUsRUFBRSxhQUFhLEVBQ3pCLEtBQUssRXRDbUZVLEtBQUssRXNDbEZwQixPQUFPLEV0Q3FHQSxHQUFHLEdzQ3BHWDs7QUFkSCxBQWdCRSxRQWhCTSxDQWdCTixDQUFDLEFBQUEsTUFBTSxHQUFHLGtCQUFrQixFQWhCOUIsUUFBUSxBQWlCTCxNQUFNLENBQUMsa0JBQWtCLENBQUMsRUFDekIsT0FBTyxFQUFFLEtBQUssRUFDZCxPQUFPLEVBQUUsQ0FBQyxFQUNWLFNBQVMsRUFBRSxzQkFBc0IsQ0FBQyxRQUFRLEdBQzNDOztBQXJCSCxBQXdCSSxRQXhCSSxBQXVCTCxjQUFjLENBQ2Isa0JBQWtCLENBQUMsRUFDakIsSUFBSSxFQUFFLElBQUksRUFDVixHQUFHLEVBQUUsR0FBRyxHQUNUOztBQTNCTCxBQTZCSSxRQTdCSSxBQXVCTCxjQUFjLENBTWIsQ0FBQyxBQUFBLE1BQU0sR0FBRyxrQkFBa0IsRUE3QmhDLFFBQVEsQUF1QkwsY0FBYyxBQU9aLE1BQU0sQ0FBQyxrQkFBa0IsQ0FBQyxFQUN6QixTQUFTLEVBQUUsa0JBQWtCLENBQUMsUUFBUSxHQUN2Qzs7QUFoQ0wsQUFvQ0ksUUFwQ0ksQUFtQ0wsZUFBZSxDQUNkLGtCQUFrQixDQUFDLEVBQ2pCLElBQUksRUFBRSxHQUFHLEVBQ1QsR0FBRyxFQUFFLElBQUksR0FDVjs7QUF2Q0wsQUF5Q0ksUUF6Q0ksQUFtQ0wsZUFBZSxDQU1kLENBQUMsQUFBQSxNQUFNLEdBQUcsa0JBQWtCLEVBekNoQyxRQUFRLEFBbUNMLGVBQWUsQUFPYixNQUFNLENBQUMsa0JBQWtCLENBQUMsRUFDekIsU0FBUyxFQUFFLGtCQUFrQixDQUFDLFFBQVEsR0FDdkM7O0FBNUNMLEFBZ0RJLFFBaERJLEFBK0NMLGFBQWEsQ0FDWixrQkFBa0IsQ0FBQyxFQUNqQixJQUFJLEVBQUUsQ0FBQyxFQUNQLEdBQUcsRUFBRSxHQUFHLEdBQ1Q7O0FBbkRMLEFBcURJLFFBckRJLEFBK0NMLGFBQWEsQ0FNWixDQUFDLEFBQUEsTUFBTSxHQUFHLGtCQUFrQixFQXJEaEMsUUFBUSxBQStDTCxhQUFhLEFBT1gsTUFBTSxDQUFDLGtCQUFrQixDQUFDLEVBQ3pCLFNBQVMsRUFBRSxzQkFBc0IsQ0FBQyxRQUFRLEdBQzNDOztBQXhETCxBQTJERSxRQTNETSxDQTJETixLQUFLLENBQUMsRTlCckROLFVBQVUsRUFBRSxDQUFDLEM4QnNEYSxNQUFLLEM5QnREVCxNQUFzQixDVExqQyxxQkFBTyxFdUM0RGhCLE1BQU0sRUFBRSxDQUFDLEdBQ1Y7O0FDOURILEFBQUEsS0FBSyxDQUFDLEVBQ0osT0FBTyxFQUFFLElBQUksRUFDYixTQUFTLEVBQUUsTUFBTSxFQUNqQixVQUFVLEVBQUUsSUFBSSxFQUNoQixNQUFNLEV2Q21EQyxNQUFLLEN1Q25ESSxDQUFDLEVBQ2pCLEtBQUssRUFBRSxJQUFJLEdBZ0VaOztBQXJFRCxBQU9FLEtBUEcsQ0FPSCxVQUFVLENBQUMsRUFDVCxJQUFJLEVBQUUsS0FBSyxFQUNYLFVBQVUsRUFBRSxDQUFDLEVBQ2IsVUFBVSxFQUFFLElBQUksRUFDaEIsVUFBVSxFQUFFLE1BQU0sRUFDbEIsUUFBUSxFQUFFLFFBQVEsR0F3RG5COztBQXBFSCxBQWNJLEtBZEMsQ0FPSCxVQUFVLEFBT1AsSUFBSyxDdkJ5SkEsWUFBWSxDdUJ6SkMsUUFBUSxDQUFDLEVBQzFCLFVBQVUsRXhDZkEsT0FBTyxFd0NnQmpCLE9BQU8sRUFBRSxFQUFFLEVBQ1gsTUFBTSxFQUFFLEdBQUcsRUFDWCxJQUFJLEVBQUUsSUFBSSxFQUNWLFFBQVEsRUFBRSxRQUFRLEVBQ2xCLEdBQUcsRUFBRSxHQUFHLEVBQ1IsS0FBSyxFQUFFLElBQUksR0FDWjs7QUF0QkwsQUF3QkksS0F4QkMsQ0FPSCxVQUFVLENBaUJSLENBQUMsQ0FBQyxFQUNBLEtBQUssRXhDekJLLE9BQU8sRXdDMEJqQixPQUFPLEVBQUUsWUFBWSxFQUNyQixPQUFPLEVBQUUsV0FBVyxFQUNwQixlQUFlLEVBQUUsSUFBSSxHQWdCdEI7O0FBNUNMLEFBOEJNLEtBOUJELENBT0gsVUFBVSxDQWlCUixDQUFDLEFBTUUsUUFBUSxDQUFDLEVBQ1IsVUFBVSxFeEMvQkYsT0FBTyxFd0NnQ2YsTUFBTSxFdkNzQkwsTUFBSyxDdUN0Qm1CLEtBQUssQ3hDOUJ4QixJQUFJLEV3QytCVixhQUFhLEVBQUUsR0FBRyxFQUNsQixPQUFPLEVBQUUsRUFBRSxFQUNYLE9BQU8sRUFBRSxLQUFLLEVBQ2QsTUFBTSxFdkNxQkwsTUFBSyxFdUNwQk4sSUFBSSxFQUFFLEdBQUcsRUFDVCxRQUFRLEVBQUUsUUFBUSxFQUNsQixHQUFHLEV2Q2dCRixNQUFLLEV1Q2ZOLFNBQVMsRUFBRSxnQkFBZ0IsRUFDM0IsS0FBSyxFdkNnQkosTUFBSyxFdUNmTixPQUFPLEV2Q3FFSixDQUFDLEd1Q3BFTDs7QUEzQ1AsQUFnRFEsS0FoREgsQ0FPSCxVQUFVLEFBdUNQLE9BQU8sQ0FDTixDQUFDLEFBQ0UsUUFBUSxDQUFDLEVBQ1IsVUFBVSxFeEMvQ04sSUFBSSxFd0NnRFIsTUFBTSxFdkNJUCxNQUFLLEN1Q0pxQixLQUFLLEN4Q2xEeEIsT0FBTyxHd0NtRGQ7O0FBbkRULEFBdURRLEtBdkRILENBT0gsVUFBVSxBQXVDUCxPQUFPLEdBUUYsVUFBVSxBQUNYLFFBQVEsQ0FBQyxFQUNSLFVBQVUsRXhDbkRMLE9BQXlCLEd3Q29EL0I7O0FBekRULEFBMkRRLEtBM0RILENBT0gsVUFBVSxBQXVDUCxPQUFPLEdBUUYsVUFBVSxDQUtaLENBQUMsQ0FBQyxFQUNBLEtBQUssRXhDekRGLE9BQXlCLEd3QzhEN0I7O0FBakVULEFBOERVLEtBOURMLENBT0gsVUFBVSxBQXVDUCxPQUFPLEdBUUYsVUFBVSxDQUtaLENBQUMsQUFHRSxRQUFRLENBQUMsRUFDUixVQUFVLEV4QzFEUCxPQUF5QixHd0MyRDdCOztBQ2hFWCxBQUFBLElBQUksQ0FBQyxFQUNILFdBQVcsRUFBRSxNQUFNLEVBQ25CLGFBQWEsRXhDbUROLE9BQU0sQ3dDbkRnQixLQUFLLEN6Q0dyQixPQUF5QixFeUNGdEMsT0FBTyxFQUFFLElBQUksRUFDYixTQUFTLEVBQUUsSUFBSSxFQUNmLFVBQVUsRUFBRSxJQUFJLEVBQ2hCLE1BQU0sRXhDaURDLE1BQUssQ3dDakRJLENBQUMsQ0FBQyxPQUF5QixDQUFDLENBQUMsR0EwRDlDOztBQWhFRCxBQVFFLElBUkUsQ0FRRixTQUFTLENBQUMsRUFDUixVQUFVLEVBQUUsQ0FBQyxHQTRCZDs7QUFyQ0gsQUFXSSxJQVhBLENBUUYsU0FBUyxDQUdQLENBQUMsQ0FBQyxFQUNBLGFBQWEsRXhDMENWLE1BQUssQ3dDMUN3QixLQUFLLENBQUMsV0FBVyxFQUNqRCxLQUFLLEVBQUUsT0FBTyxFQUNkLE9BQU8sRUFBRSxLQUFLLEVBQ2QsTUFBTSxFQUFFLENBQUMsQ3hDeUNOLE1BQUssQ3dDekNVLENBQUMsQ0FBQyxDQUFDLEVBQ3JCLE9BQU8sRXhDd0NKLE1BQUssQ0FETCxNQUFLLEN3Q3ZDaUIsTUFBMEIsQ3hDdUNoRCxNQUFLLEV3Q3RDUixlQUFlLEVBQUUsSUFBSSxHQUt0Qjs7QUF0QkwsQUFrQk0sSUFsQkYsQ0FRRixTQUFTLENBR1AsQ0FBQyxBQU9FLE1BQU0sRUFsQmIsSUFBSSxDQVFGLFNBQVMsQ0FHUCxDQUFDLEFBUUUsTUFBTSxDQUFDLEVBQ04sS0FBSyxFekNwQkcsT0FBTyxHeUNxQmhCOztBQXJCUCxBQXVCSSxJQXZCQSxDQVFGLFNBQVMsQUFlTixPQUFPLENBQUMsQ0FBQyxFQXZCZCxJQUFJLENBUUYsU0FBUyxDQWdCUCxDQUFDLEFBQUEsT0FBTyxDQUFDLEVBQ1AsbUJBQW1CLEV6Q3pCVCxPQUFPLEV5QzBCakIsS0FBSyxFekMxQkssT0FBTyxHeUMyQmxCOztBQTNCTCxBQTZCSSxJQTdCQSxDQVFGLFNBQVMsQUFxQk4sV0FBVyxDQUFDLEVBQ1gsSUFBSSxFQUFFLFFBQVEsRUFDZCxVQUFVLEVBQUUsS0FBSyxHQUNsQjs7QUFoQ0wsQUFrQ0ksSUFsQ0EsQ0FRRixTQUFTLENBMEJQLFVBQVUsQ0FBQyxFQUNULFVBQVUsRXhDb0JQLE9BQUssR3dDbkJUOztBQXBDTCxBQXdDSSxJQXhDQSxBQXVDRCxVQUFVLENBQ1QsU0FBUyxDQUFDLEVBQ1IsSUFBSSxFQUFFLEtBQUssRUFDWCxVQUFVLEVBQUUsTUFBTSxHQWNuQjs7QUF4REwsQUE0Q00sSUE1Q0YsQUF1Q0QsVUFBVSxDQUNULFNBQVMsQ0FJUCxDQUFDLENBQUMsRUFDQSxNQUFNLEVBQUUsQ0FBQyxHQUNWOztBQTlDUCxBQWlEUSxJQWpESixBQXVDRCxVQUFVLENBQ1QsU0FBUyxDQVFQLE1BQU0sQ0FDSCxBQUFBLFVBQUMsQUFBQSxDQUFXLE9BQU8sQ0FBQyxFQUNuQixRQUFRLEVBQUUsUUFBUSxFQUNsQixLQUFLLEV4Q0dOLE1BQUssRXdDRkosR0FBRyxFeENFSixNQUFLLEV3Q0RKLFNBQVMsRUFBRSxlQUFlLEdBQzNCOztBQXREVCxBQTRESSxJQTVEQSxBQTJERCxJQUFLLENBQUEsVUFBVSxFQUNkLE1BQU0sQ0FBQyxFQUNMLGFBQWEsRUFBRSxDQUFDLEdBQ2pCOztBQzlETCxBQUFBLEtBQUssQ0FBQyxFQUNKLGFBQWEsRUFBRSxhQUFhLEVBQzVCLFdBQVcsRUFBRSxVQUFVLEVBQ3ZCLE9BQU8sRUFBRSxJQUFJLEdBaUNkOztBQXBDRCxBQUtFLEtBTEcsQ0FLSCxVQUFVLEVBTFosS0FBSyxDQU1ILFlBQVksQ0FBQyxFQUNYLElBQUksRUFBRSxRQUFRLEdBQ2Y7O0FBUkgsQUFTRSxLQVRHLENBU0gsYUFBYSxDQUFDLEVBQ1osSUFBSSxFQUFFLFFBQVEsR0FPZjs7QUFqQkgsQUFXSSxLQVhDLENBU0gsYUFBYSxBQUVWLElBQUssQ3pCNEpBLFlBQVksRXlCNUpFLEVBQ2xCLFlBQVksRXpDNENULE1BQUssR3lDM0NUOztBQWJMLEFBY0ksS0FkQyxDQVNILGFBQWEsQUFLVixJQUFLLEN6QnFKWSxXQUFXLEV5QnJKVixFQUNqQixhQUFhLEV6Q3lDVixNQUFLLEd5Q3hDVDs7QUFoQkwsQUFrQkUsS0FsQkcsQ0FrQkgsV0FBVyxFQWxCYixLQUFLLENBbUJILGNBQWMsQ0FBQyxFQUNiLFdBQVcsRXpDc0RELE1BQU0sR3lDckRqQjs7QUFyQkgsQUF1QkUsS0F2QkcsQUF1QkYsY0FBYyxDQUFDLEVBQ2QsV0FBVyxFQUFFLE1BQU0sR0FXcEI7O0FBbkNILEFBMEJJLEtBMUJDLEFBdUJGLGNBQWMsQ0FHYixhQUFhLENBQUMsRUFDWixRQUFRLEVBQUUsTUFBTSxHQUNqQjs7QUE1QkwsQUE4QkksS0E5QkMsQUF1QkYsY0FBYyxDQU9iLFdBQVcsRUE5QmYsS0FBSyxBQXVCRixjQUFjLENBUWIsY0FBYyxDQUFDLEVoQzlCakIsUUFBUSxFQUFFLE1BQU0sRUFDaEIsYUFBYSxFQUFFLFFBQVEsRUFDdkIsV0FBVyxFQUFFLE1BQU0sRWdDOEJmLGFBQWEsRUFBRSxDQUFDLEdBQ2pCOztBQ2xDTCxBQUFBLE1BQU0sQ0FBQyxFaENDTCxVQUFVLEVYQUMsc0JBQU8sRVdDbEIsWUFBWSxFWERELE9BQU8sRTJDQ2xCLE1BQU0sRTFDbURDLE9BQU0sQzBDbkRTLEtBQUssQzNDRGhCLE9BQU8sRTJDRWxCLGFBQWEsRTFDbUROLE1BQUssRTBDbERaLEtBQUssRTNDRk8sSUFBSSxFMkNHaEIsT0FBTyxFQUFFLEtBQUssRUFDZCxPQUFPLEUxQ2tEQSxNQUFLLEUwQ2pEWixLQUFLLEVBQUUsSUFBSSxHQXVDWjs7QUE5Q0QsQUFTRSxNQVRJLEFBU0gsY0FBYyxDQUFDLEVoQ1JoQixVQUFVLEVYREksd0JBQU8sRVdFckIsWUFBWSxFWEZFLE9BQU8sRzJDV3BCOztBQVhILEFBYUUsTUFiSSxBQWFILGNBQWMsQ0FBQyxFaENaaEIsVUFBVSxFVjJCSSx1QkFBTyxFVTFCckIsWUFBWSxFVjBCRSxPQUFPLEcwQ2JwQjs7QUFmSCxBQWlCRSxNQWpCSSxBQWlCSCxjQUFjLENBQUMsRWhDaEJoQixVQUFVLEVWNEJJLHVCQUFPLEVVM0JyQixZQUFZLEVWMkJFLE9BQU8sRzBDVnBCOztBQW5CSCxBQXFCRSxNQXJCSSxBQXFCSCxZQUFZLENBQUMsRWhDcEJkLFVBQVUsRVY2QkUsc0JBQU8sRVU1Qm5CLFlBQVksRVY0QkEsT0FBTyxHMENQbEI7O0FBdkJILEFBeUJFLE1BekJJLENBeUJKLENBQUMsQ0FBQyxFQUNBLEtBQUssRTNDeEJLLElBQUksRTJDeUJkLGVBQWUsRUFBRSxTQUFTLEdBUTNCOztBQW5DSCxBQTZCSSxNQTdCRSxDQXlCSixDQUFDLEFBSUUsTUFBTSxFQTdCWCxNQUFNLENBeUJKLENBQUMsQUFLRSxNQUFNLEVBOUJYLE1BQU0sQ0F5QkosQ0FBQyxBQU1FLE9BQU8sRUEvQlosTUFBTSxDQXlCSixDQUFDLEFBT0UsT0FBTyxDQUFDLEVBQ1AsT0FBTyxFQUFFLEdBQUcsR0FDYjs7QUFsQ0wsQUFxQ0UsTUFyQ0ksQ0FxQ0osVUFBVSxDQUFDLEVBQ1QsTUFBTSxFMUNnQkQsTUFBSyxHMENmWDs7QUF2Q0gsQUEwQ0ksTUExQ0UsQ0F5Q0osQ0FBQyxBQUNFLFdBQVcsQ0FBQyxFQUNYLGFBQWEsRUFBRSxDQUFDLEdBQ2pCOztBQzVDTCxBQUFBLFFBQVEsQ0FBQyxFQUNQLFFBQVEsRUFBRSxRQUFRLEdBNEVuQjs7QUE3RUQsQUFFRSxRQUZNLEFBRUwsT0FBTyxDQUFDLEVBQ1AsVUFBVSxFNUNGRCxzQkFBTyxFNENHaEIsYUFBYSxFM0NrRFIsTUFBSyxFMkNqRFYsTUFBTSxFQUFFLElBQUksRUFDWixLQUFLLEU1Q0pLLElBQUksRTRDS2QsT0FBTyxFQUFFLGtCQUFrQixFQUMzQixPQUFPLEVBQUUsS0FBSyxFQUNkLFNBQVMsRTNDK0RFLE1BQUssRTJDOURoQixJQUFJLEVBQUUsR0FBRyxFQUNULFNBQVMsRTNDb0ZNLEtBQUssRTJDbkZwQixPQUFPLEVBQUUsQ0FBQyxFQUNWLFFBQVEsRUFBRSxNQUFNLEVBQ2hCLE9BQU8sRTNDeUNGLE1BQUssQ0FDTCxNQUFLLEUyQ3pDVixjQUFjLEVBQUUsSUFBSSxFQUNwQixRQUFRLEVBQUUsUUFBUSxFQUNsQixhQUFhLEVBQUUsUUFBUSxFQUN2QixTQUFTLEVBQUUsdUJBQXdCLEVBQ25DLFVBQVUsRUFBRSwwQkFBMEIsRUFDdEMsV0FBVyxFQUFFLEdBQUcsRUFDaEIsT0FBTyxFM0M2RkEsR0FBRyxHMkM1Rlg7O0FBdEJILEFBeUJJLFFBekJJLEFBdUJMLE1BQU0sQUFFSixPQUFPLEVBekJaLFFBQVEsQUF3QkwsTUFBTSxBQUNKLE9BQU8sQ0FBQyxFQUNQLE9BQU8sRUFBRSxDQUFDLEVBQ1YsU0FBUyxFQUFFLHdCQUF5QixHQUNyQzs7QUE1QkwsQUE4QkUsUUE5Qk0sQ0E4QkwsQUFBQSxRQUFDLEFBQUEsR0E5QkosUUFBUSxBQStCTCxTQUFTLENBQUMsRUFDVCxjQUFjLEVBQUUsSUFBSSxHQUNyQjs7QUFqQ0gsQUFvQ0ksUUFwQ0ksQUFtQ0wsY0FBYyxBQUNaLE9BQU8sQ0FBQyxFQUNQLE1BQU0sRUFBRSxHQUFHLEVBQ1gsSUFBSSxFQUFFLElBQUksRUFDVixTQUFTLEVBQUUsdUJBQXdCLEdBQ3BDOztBQXhDTCxBQTJDTSxRQTNDRSxBQW1DTCxjQUFjLEFBTVosTUFBTSxBQUVKLE9BQU8sRUEzQ2QsUUFBUSxBQW1DTCxjQUFjLEFBT1osTUFBTSxBQUNKLE9BQU8sQ0FBQyxFQUNQLFNBQVMsRUFBRSxzQkFBdUIsR0FDbkM7O0FBN0NQLEFBa0RJLFFBbERJLEFBaURMLGVBQWUsQUFDYixPQUFPLENBQUMsRUFDUCxNQUFNLEVBQUUsSUFBSSxFQUNaLEdBQUcsRUFBRSxJQUFJLEVBQ1QsU0FBUyxFQUFFLHdCQUF5QixHQUNyQzs7QUF0REwsQUF5RE0sUUF6REUsQUFpREwsZUFBZSxBQU1iLE1BQU0sQUFFSixPQUFPLEVBekRkLFFBQVEsQUFpREwsZUFBZSxBQU9iLE1BQU0sQUFDSixPQUFPLENBQUMsRUFDUCxTQUFTLEVBQUUsdUJBQXdCLEdBQ3BDOztBQTNEUCxBQWdFSSxRQWhFSSxBQStETCxhQUFhLEFBQ1gsT0FBTyxDQUFDLEVBQ1AsTUFBTSxFQUFFLEdBQUcsRUFDWCxJQUFJLEVBQUUsSUFBSSxFQUNWLEtBQUssRUFBRSxJQUFJLEVBQ1gsU0FBUyxFQUFFLHNCQUF1QixHQUNuQzs7QUFyRUwsQUF3RU0sUUF4RUUsQUErREwsYUFBYSxBQU9YLE1BQU0sQUFFSixPQUFPLEVBeEVkLFFBQVEsQUErREwsYUFBYSxBQVFYLE1BQU0sQUFDSixPQUFPLENBQUMsRUFDUCxTQUFTLEVBQUUsdUJBQXdCLEdBQ3BDOztBQzFFUCxVQUFVLENBQVYsT0FBVSxHQUNSLEVBQUUsR0FDQSxTQUFTLEVBQUUsWUFBWTtFQUV6QixJQUFJLEdBQ0YsU0FBUyxFQUFFLGNBQWM7O0FBSTdCLFVBQVUsQ0FBVixVQUFVLEdBQ1IsRUFBRSxHQUNBLE9BQU8sRUFBRSxDQUFDO0lBQ1YsU0FBUyxFQUFFLG1CQUFvQjtFQUVqQyxJQUFJLEdBQ0YsT0FBTyxFQUFFLENBQUM7SUFDVixTQUFTLEVBQUUsYUFBYTs7QXZDSjFCLEFBQUEsYUFBYSxDQUFKLEVBQ1AsS0FBSyxFTmJPLE9BQU8sQ01hTCxVQUFVLEdBQ3pCOztBQUVELEFBQ0UsQ0FERCxBQUFBLGFBQWEsQUFDWCxNQUFNLEVBRFQsQ0FBQyxBQUFBLGFBQWEsQUFFWCxNQUFNLENBQUMsRUFDTixLQUFLLEVBQUUsT0FBa0IsR0FDMUI7O0FBSkgsQUFLRSxDQUxELEFBQUEsYUFBYSxBQUtYLFFBQVEsQ0FBQyxFQUNSLEtBQUssRUFBRSxPQUFtQixHQUMzQjs7QUFYSCxBQUFBLGVBQWUsQ0FBTixFQUNQLEtBQUssRUxIYyxPQUE0QixDS0dqQyxVQUFVLEdBQ3pCOztBQUVELEFBQ0UsQ0FERCxBQUFBLGVBQWUsQUFDYixNQUFNLEVBRFQsQ0FBQyxBQUFBLGVBQWUsQUFFYixNQUFNLENBQUMsRUFDTixLQUFLLEVBQUUsT0FBa0IsR0FDMUI7O0FBSkgsQUFLRSxDQUxELEFBQUEsZUFBZSxBQUtiLFFBQVEsQ0FBQyxFQUNSLEtBQUssRUFBRSxPQUFtQixHQUMzQjs7QUFYSCxBQUFBLFVBQVUsQ0FBRCxFQUNQLEtBQUssRU5WSSxPQUF5QixDTVVwQixVQUFVLEdBQ3pCOztBQUVELEFBQ0UsQ0FERCxBQUFBLFVBQVUsQUFDUixNQUFNLEVBRFQsQ0FBQyxBQUFBLFVBQVUsQUFFUixNQUFNLENBQUMsRUFDTixLQUFLLEVBQUUsT0FBa0IsR0FDMUI7O0FBSkgsQUFLRSxDQUxELEFBQUEsVUFBVSxBQUtSLFFBQVEsQ0FBQyxFQUNSLEtBQUssRUFBRSxPQUFtQixHQUMzQjs7QUFYSCxBQUFBLFdBQVcsQ0FBRixFQUNQLEtBQUssRU5YSyxJQUFJLENNV0EsVUFBVSxHQUN6Qjs7QUFFRCxBQUNFLENBREQsQUFBQSxXQUFXLEFBQ1QsTUFBTSxFQURULENBQUMsQUFBQSxXQUFXLEFBRVQsTUFBTSxDQUFDLEVBQ04sS0FBSyxFQUFFLE9BQWtCLEdBQzFCOztBQUpILEFBS0UsQ0FMRCxBQUFBLFdBQVcsQUFLVCxRQUFRLENBQUMsRUFDUixLQUFLLEVBQUUsS0FBbUIsR0FDM0I7O0FBWEgsQUFBQSxVQUFVLENBQUQsRUFDUCxLQUFLLEVOTlMsT0FBd0IsQ01NeEIsVUFBVSxHQUN6Qjs7QUFFRCxBQUNFLENBREQsQUFBQSxVQUFVLEFBQ1IsTUFBTSxFQURULENBQUMsQUFBQSxVQUFVLEFBRVIsTUFBTSxDQUFDLEVBQ04sS0FBSyxFQUFFLE9BQWtCLEdBQzFCOztBQUpILEFBS0UsQ0FMRCxBQUFBLFVBQVUsQUFLUixRQUFRLENBQUMsRUFDUixLQUFLLEVBQUUsT0FBbUIsR0FDM0I7O0FBWEgsQUFBQSxhQUFhLENBQUosRUFDUCxLQUFLLEVMZU8sT0FBTyxDS2ZMLFVBQVUsR0FDekI7O0FBRUQsQUFDRSxDQURELEFBQUEsYUFBYSxBQUNYLE1BQU0sRUFEVCxDQUFDLEFBQUEsYUFBYSxBQUVYLE1BQU0sQ0FBQyxFQUNOLEtBQUssRUFBRSxPQUFrQixHQUMxQjs7QUFKSCxBQUtFLENBTEQsQUFBQSxhQUFhLEFBS1gsUUFBUSxDQUFDLEVBQ1IsS0FBSyxFQUFFLE9BQW1CLEdBQzNCOztBQVhILEFBQUEsYUFBYSxDQUFKLEVBQ1AsS0FBSyxFTGdCTyxPQUFPLENLaEJMLFVBQVUsR0FDekI7O0FBRUQsQUFDRSxDQURELEFBQUEsYUFBYSxBQUNYLE1BQU0sRUFEVCxDQUFDLEFBQUEsYUFBYSxBQUVYLE1BQU0sQ0FBQyxFQUNOLEtBQUssRUFBRSxPQUFrQixHQUMxQjs7QUFKSCxBQUtFLENBTEQsQUFBQSxhQUFhLEFBS1gsUUFBUSxDQUFDLEVBQ1IsS0FBSyxFQUFFLE9BQW1CLEdBQzNCOztBQVhILEFBQUEsV0FBVyxDQUFGLEVBQ1AsS0FBSyxFTGlCSyxPQUFPLENLakJILFVBQVUsR0FDekI7O0FBRUQsQUFDRSxDQURELEFBQUEsV0FBVyxBQUNULE1BQU0sRUFEVCxDQUFDLEFBQUEsV0FBVyxBQUVULE1BQU0sQ0FBQyxFQUNOLEtBQUssRUFBRSxPQUFrQixHQUMxQjs7QUFKSCxBQUtFLENBTEQsQUFBQSxXQUFXLEFBS1QsUUFBUSxDQUFDLEVBQ1IsS0FBSyxFQUFFLE9BQW1CLEdBQzNCOztBQXRCSCxBQUFBLFdBQVcsQ0FBRixFQUNQLFVBQVUsRU5GRSxPQUFPLENNRUEsVUFBVSxFQUczQixLQUFLLEVOSEcsSUFBSSxHTUtmOztBQU5ELEFBQUEsYUFBYSxDQUFKLEVBQ1AsVUFBVSxFTE9JLE9BQThCLENLUHpCLFVBQVUsR0FLOUI7O0FBTkQsQUFBQSxRQUFRLENBQUMsRUFDUCxVQUFVLEVOREQsT0FBTyxDTUNHLFVBQVUsRUFHM0IsS0FBSyxFTkhHLElBQUksR01LZjs7QUFORCxBQUFBLFFBQVEsQ0FBQyxFQUNQLFVBQVUsRU5JSCxPQUF5QixDTUpiLFVBQVUsR0FLOUI7O0FBTkQsQUFBQSxXQUFXLENBQUYsRUFDUCxVQUFVLEVMMEJFLE9BQU8sQ0sxQkEsVUFBVSxFQUczQixLQUFLLEVOSEcsSUFBSSxHTUtmOztBQU5ELEFBQUEsV0FBVyxDQUFGLEVBQ1AsVUFBVSxFTDJCRSxPQUFPLENLM0JBLFVBQVUsRUFHM0IsS0FBSyxFTkhHLElBQUksR01LZjs7QUFORCxBQUFBLFNBQVMsQ0FBQSxFQUNQLFVBQVUsRUw0QkEsT0FBTyxDSzVCRSxVQUFVLEVBRzNCLEtBQUssRU5IRyxJQUFJLEdNS2Y7O0EwQ1BILEFBQUEsT0FBTyxDQUFDLEVBQ04sTUFBTSxFQUFFLE9BQU8sR0FDaEI7O0FBRUQsQUFBQSxPQUFPLENBQUMsRUFDTixNQUFNLEVBQUUsSUFBSSxHQUNiOztBQUVELEFBQUEsVUFBVSxDQUFDLEVBQ1QsTUFBTSxFQUFFLE9BQU8sR0FDaEI7O0FBRUQsQUFBQSxXQUFXLENBQUMsRUFDVixNQUFNLEVBQUUsUUFBUSxHQUNqQjs7QUFFRCxBQUFBLGNBQWMsQ0FBQyxFQUNiLE1BQU0sRUFBRSxXQUFXLEdBQ3BCOztBQUVELEFBQUEsT0FBTyxDQUFDLEVBQ04sTUFBTSxFQUFFLElBQUksR0FDYjs7QUN0QkQsQUFBQSxRQUFRLENBQUMsRUFDUCxPQUFPLEVBQUUsS0FBSyxHQUNmOztBQUNELEFBQUEsU0FBUyxDQUFDLEVBQ1IsT0FBTyxFQUFFLE1BQU0sR0FDaEI7O0FBQ0QsQUFBQSxlQUFlLENBQUMsRUFDZCxPQUFPLEVBQUUsWUFBWSxHQUN0Qjs7QUFDRCxBQUFBLE9BQU8sQ0FBQyxFQUNOLE9BQU8sRUFBRSxJQUFJLEdBQ2Q7O0FBQ0QsQUFBQSxjQUFjLENBQUMsRUFDYixPQUFPLEVBQUUsV0FBVyxHQUNyQjs7QUFDRCxBQUFBLE9BQU8sRUFDUCxPQUFPLENBQUMsRUFDTixPQUFPLEVBQUUsZUFBZSxHQUN6Qjs7QUFDRCxBQUFBLFVBQVUsQ0FBQyxFQUNULFVBQVUsRUFBRSxPQUFPLEdBQ3BCOztBQUNELEFBQUEsWUFBWSxDQUFDLEVBQ1gsVUFBVSxFQUFFLE1BQU0sR0FDbkI7O0FBQ0QsQUFBQSxVQUFVLENBQUMsRUFDVCxVQUFVLEVBQUUsV0FBVyxFQUN2QixNQUFNLEVBQUUsQ0FBQyxFQUNULEtBQUssRUFBRSxXQUFXLEVBQ2xCLFNBQVMsRUFBRSxDQUFDLEVBQ1osV0FBVyxFQUFFLENBQUMsRUFDZCxXQUFXLEVBQUUsSUFBSSxHQUNsQjs7QUFDRCxBQUFBLGVBQWUsQ0FBQyxFQUNkLE1BQU0sRUFBRSxDQUFDLEVBQ1QsSUFBSSxFQUFFLGdCQUFhLEVBQ25CLE1BQU0sRUFBRSxHQUFHLEVBQ1gsTUFBTSxFQUFFLElBQUksRUFDWixRQUFRLEVBQUUsTUFBTSxFQUNoQixPQUFPLEVBQUUsQ0FBQyxFQUNWLFFBQVEsRUFBRSxRQUFRLEVBQ2xCLEtBQUssRUFBRSxHQUFHLEdBQ1g7O0FDMUNELEFBQUEsUUFBUSxFQUNSLGFBQWEsQ0FBQyxFQUNaLE9BQU8sRUFBRSxLQUFLLEVBQ2QsUUFBUSxFQUFFLFFBQVEsR0FXbkI7O0FBZEQsQUFLRSxRQUxNLENBS0wsQUFBQSxZQUFDLEFBQUEsQ0FBYSxPQUFPLEVBSnhCLGFBQWEsQ0FJVixBQUFBLFlBQUMsQUFBQSxDQUFhLE9BQU8sQ0FBQyxFQUNyQixVQUFVLEVsREpBLElBQUksRWtES2QsS0FBSyxFbERKSSxPQUF5QixFa0RLbEMsT0FBTyxFQUFFLGtCQUFrQixFQUMzQixPQUFPLEVBQUUsWUFBWSxFQUNyQixTQUFTLEVqRDhERSxNQUFLLEVpRDdEaEIsT0FBTyxFQUFFLENBQUMsQ2pENkNMLE1BQUssRWlENUNWLFNBQVMsRUFBRSxvQkFBMEMsR0FDdEQ7O0FBR0gsQUFBQSxRQUFRLENBQUMsRUFDUCxVQUFVLEVqRG9DSCxPQUFNLENpRHBDYSxLQUFLLENqREtaLE9BQTBCLEVpREo3QyxNQUFNLEVqRG1DQyxPQUFNLEVpRGxDYixNQUFNLEVqRHFDQyxNQUFLLENpRHJDSSxDQUFDLEdBS2xCOztBQVJELEFBS0UsUUFMTSxDQUtMLEFBQUEsWUFBQyxBQUFBLEVBQWMsRUFDZCxNQUFNLEVqRG9DRCxNQUFLLENpRHBDTSxDQUFDLEdBQ2xCOztBQUdILEFBQUEsYUFBYSxDQUFDLEVBQ1osT0FBTyxFQUFFLEtBQUssRUFDZCxPQUFPLEVqRDhCQSxNQUFLLEdpRFZiOztBQXRCRCxBQUlFLGFBSlcsQUFJVixRQUFRLENBQUMsRUFDUixXQUFXLEVqRHNCTixPQUFNLENpRHRCZ0IsS0FBSyxDbEQxQnJCLE9BQXlCLEVrRDJCcEMsTUFBTSxFakR3QkQsTUFBSyxFaUR2QlYsT0FBTyxFQUFFLEVBQUUsRUFDWCxPQUFPLEVBQUUsS0FBSyxFQUNkLElBQUksRUFBRSxHQUFHLEVBQ1QsUUFBUSxFQUFFLFFBQVEsRUFDbEIsR0FBRyxFakRtQkUsTUFBSyxFaURsQlYsU0FBUyxFQUFFLGdCQUFnQixHQUM1Qjs7QUFiSCxBQWVFLGFBZlcsQ0FlVixBQUFBLFlBQUMsQUFBQSxDQUFhLE9BQU8sQ0FBQyxFQUNyQixJQUFJLEVBQUUsR0FBRyxFQUNULE9BQU8sRWpEWUYsTUFBSyxDaURaTyxDQUFDLEVBQ2xCLFFBQVEsRUFBRSxRQUFRLEVBQ2xCLEdBQUcsRUFBRSxHQUFHLEVBQ1IsU0FBUyxFQUFFLHFCQUFxQixHQUNqQzs7QUMvQ0gsQUFBQSxRQUFRLENBQUMsRUFDUCxLQUFLLEVBQUUsc0JBQXNCLEVBQzdCLFVBQVUsRWxEd0RILE1BQUssRWtEdkRaLGNBQWMsRUFBRSxJQUFJLEVBQ3BCLFFBQVEsRUFBRSxRQUFRLEdBNEJuQjs7QUFoQ0QsQUFLRSxRQUxNLEFBS0wsT0FBTyxDQUFDLEVBQ1AsU0FBUyxFQUFFLDZCQUE2QixFQUN4QyxNQUFNLEVsRCtDRCxNQUFLLENrRC9DZSxLQUFLLENuRFBsQixPQUFPLEVtRFFuQixhQUFhLEVBQUUsR0FBRyxFQUNsQixrQkFBa0IsRUFBRSxXQUFXLEVBQy9CLGdCQUFnQixFQUFFLFdBQVcsRUFDN0IsT0FBTyxFQUFFLEVBQUUsRUFDWCxPQUFPLEVBQUUsS0FBSyxFQUNkLE1BQU0sRWxENkNELE1BQUssRWtENUNWLElBQUksRUFBRSxHQUFHLEVBQ1QsV0FBVyxFbER5Q04sT0FBSyxFa0R4Q1YsVUFBVSxFbER3Q0wsT0FBSyxFa0R2Q1YsUUFBUSxFQUFFLFFBQVEsRUFDbEIsR0FBRyxFQUFFLEdBQUcsRUFDUixLQUFLLEVsRHVDQSxNQUFLLEVrRHRDVixPQUFPLEVsRDJGQSxDQUFDLEdrRDFGVDs7QUFyQkgsQUF1QkUsUUF2Qk0sQUF1QkwsV0FBVyxDQUFDLEVBQ1gsVUFBVSxFbER3Q0osSUFBSSxHa0RqQ1g7O0FBL0JILEFBeUJJLFFBekJJLEFBdUJMLFdBQVcsQUFFVCxPQUFPLENBQUMsRUFDUCxNQUFNLEVsRG9DSCxNQUFNLEVrRG5DVCxXQUFXLEVsRCtCUixPQUFLLEVrRDlCUixVQUFVLEVsRDhCUCxPQUFLLEVrRDdCUixLQUFLLEVsRGlDRixNQUFNLEdrRGhDVjs7QUM5QkwsQS9DQ0UsUytDRE8sQS9DQ04sT0FBTyxDQUFDLEVBQ1AsS0FBSyxFQUFFLElBQUksRUFDWCxPQUFPLEVBQUUsRUFBRSxFQUNYLE9BQU8sRUFBRSxLQUFLLEdBQ2Y7O0ErQ0RILEFBQUEsV0FBVyxDQUFDLEVBQ1YsS0FBSyxFQUFFLGVBQWUsR0FDdkI7O0FBRUQsQUFBQSxZQUFZLENBQUMsRUFDWCxLQUFLLEVBQUUsZ0JBQWdCLEdBQ3hCOztBQUVELEFBQUEsV0FBVyxDQUFDLEVBQ1YsUUFBUSxFQUFFLG1CQUFtQixHQUM5Qjs7QUFFRCxBQUFBLFdBQVcsQ0FBQyxFQUNWLFFBQVEsRUFBRSxtQkFBbUIsR0FDOUI7O0FBRUQsQUFBQSxRQUFRLENBQUMsRUFDUCxRQUFRLEVBQUUsZ0JBQWdCLEdBQzNCOztBQUVELEFBQUEsU0FBUyxDQUFDLEVBQ1IsUUFBUSxFQUFFLGlCQUFpQixHQUM1Qjs7QUFFRCxBQUFBLFdBQVcsQ0FBQyxFQUNWLE9BQU8sRUFBRSxLQUFLLEVBQ2QsS0FBSyxFQUFFLElBQUksRUFDWCxXQUFXLEVBQUUsSUFBSSxFQUNqQixZQUFZLEVBQUUsSUFBSSxHQUNuQjs7QUFFRCxBQUFBLGNBQWMsQ0FBQyxFQUNiLFdBQVcsRUFBRSxNQUFNLEVBQ25CLE9BQU8sRUFBRSxJQUFJLEVBQ2IsZUFBZSxFQUFFLE1BQU0sR0FDeEI7O0E1Q3RDQyxBQUFBLElBQUksQ0FBTSxFQUNSLE1BQU0sRTRDd0NpQixDQUFDLEM1Q3hDVixVQUFVLEdBQ3pCOztBQUVELEFBQUEsS0FBSyxDQUFNLEVBQ1QsYUFBYSxFNENvQ1UsQ0FBQyxDNUNwQ0gsVUFBVSxHQUNoQzs7QUFFRCxBQUFBLEtBQUssQ0FBTSxFQUNULFdBQVcsRTRDZ0NZLENBQUMsQzVDaENMLFVBQVUsR0FDOUI7O0FBRUQsQUFBQSxLQUFLLENBQU0sRUFDVCxZQUFZLEU0QzRCVyxDQUFDLEM1QzVCSixVQUFVLEdBQy9COztBQUVELEFBQUEsS0FBSyxDQUFNLEVBQ1QsVUFBVSxFNEN3QmEsQ0FBQyxDNUN4Qk4sVUFBVSxHQUM3Qjs7QUFFRCxBQUFBLEtBQUssQ0FBTSxFQUNULFdBQVcsRTRDb0JZLENBQUMsQzVDcEJMLFVBQVUsRUFDN0IsWUFBWSxFNENtQlcsQ0FBQyxDNUNuQkosVUFBVSxHQUMvQjs7QUFFRCxBQUFBLEtBQUssQ0FBTSxFQUNULGFBQWEsRTRDZVUsQ0FBQyxDNUNmSCxVQUFVLEVBQy9CLFVBQVUsRTRDY2EsQ0FBQyxDNUNkTixVQUFVLEdBQzdCOztBQTVCRCxBQUFBLElBQUksQ0FBTSxFQUNSLE1BQU0sRVBxREQsTUFBSyxDT3JESSxVQUFVLEdBQ3pCOztBQUVELEFBQUEsS0FBSyxDQUFNLEVBQ1QsYUFBYSxFUGlEUixNQUFLLENPakRXLFVBQVUsR0FDaEM7O0FBRUQsQUFBQSxLQUFLLENBQU0sRUFDVCxXQUFXLEVQNkNOLE1BQUssQ083Q1MsVUFBVSxHQUM5Qjs7QUFFRCxBQUFBLEtBQUssQ0FBTSxFQUNULFlBQVksRVB5Q1AsTUFBSyxDT3pDVSxVQUFVLEdBQy9COztBQUVELEFBQUEsS0FBSyxDQUFNLEVBQ1QsVUFBVSxFUHFDTCxNQUFLLENPckNRLFVBQVUsR0FDN0I7O0FBRUQsQUFBQSxLQUFLLENBQU0sRUFDVCxXQUFXLEVQaUNOLE1BQUssQ09qQ1MsVUFBVSxFQUM3QixZQUFZLEVQZ0NQLE1BQUssQ09oQ1UsVUFBVSxHQUMvQjs7QUFFRCxBQUFBLEtBQUssQ0FBTSxFQUNULGFBQWEsRVA0QlIsTUFBSyxDTzVCVyxVQUFVLEVBQy9CLFVBQVUsRVAyQkwsTUFBSyxDTzNCUSxVQUFVLEdBQzdCOztBQTVCRCxBQUFBLElBQUksQ0FBTSxFQUNSLE1BQU0sRVBzREQsTUFBSyxDT3RESSxVQUFVLEdBQ3pCOztBQUVELEFBQUEsS0FBSyxDQUFNLEVBQ1QsYUFBYSxFUGtEUixNQUFLLENPbERXLFVBQVUsR0FDaEM7O0FBRUQsQUFBQSxLQUFLLENBQU0sRUFDVCxXQUFXLEVQOENOLE1BQUssQ085Q1MsVUFBVSxHQUM5Qjs7QUFFRCxBQUFBLEtBQUssQ0FBTSxFQUNULFlBQVksRVAwQ1AsTUFBSyxDTzFDVSxVQUFVLEdBQy9COztBQUVELEFBQUEsS0FBSyxDQUFNLEVBQ1QsVUFBVSxFUHNDTCxNQUFLLENPdENRLFVBQVUsR0FDN0I7O0FBRUQsQUFBQSxLQUFLLENBQU0sRUFDVCxXQUFXLEVQa0NOLE1BQUssQ09sQ1MsVUFBVSxFQUM3QixZQUFZLEVQaUNQLE1BQUssQ09qQ1UsVUFBVSxHQUMvQjs7QUFFRCxBQUFBLEtBQUssQ0FBTSxFQUNULGFBQWEsRVA2QlIsTUFBSyxDTzdCVyxVQUFVLEVBQy9CLFVBQVUsRVA0QkwsTUFBSyxDTzVCUSxVQUFVLEdBQzdCOztBQUtELEFBQUEsSUFBSSxDQUFNLEVBQ1IsT0FBTyxFNENhaUIsQ0FBQyxDNUNiVixVQUFVLEdBQzFCOztBQUVELEFBQUEsS0FBSyxDQUFNLEVBQ1QsY0FBYyxFNENTVSxDQUFDLEM1Q1RILFVBQVUsR0FDakM7O0FBRUQsQUFBQSxLQUFLLENBQU0sRUFDVCxZQUFZLEU0Q0tZLENBQUMsQzVDTEwsVUFBVSxHQUMvQjs7QUFFRCxBQUFBLEtBQUssQ0FBTSxFQUNULGFBQWEsRTRDQ1csQ0FBQyxDNUNESixVQUFVLEdBQ2hDOztBQUVELEFBQUEsS0FBSyxDQUFNLEVBQ1QsV0FBVyxFNENIYSxDQUFDLEM1Q0dOLFVBQVUsR0FDOUI7O0FBRUQsQUFBQSxLQUFLLENBQU0sRUFDVCxZQUFZLEU0Q1BZLENBQUMsQzVDT0wsVUFBVSxFQUM5QixhQUFhLEU0Q1JXLENBQUMsQzVDUUosVUFBVSxHQUNoQzs7QUFFRCxBQUFBLEtBQUssQ0FBTSxFQUNULGNBQWMsRTRDWlUsQ0FBQyxDNUNZSCxVQUFVLEVBQ2hDLFdBQVcsRTRDYmEsQ0FBQyxDNUNhTixVQUFVLEdBQzlCOztBQTVCRCxBQUFBLElBQUksQ0FBTSxFQUNSLE9BQU8sRVBvQkYsTUFBSyxDT3BCSyxVQUFVLEdBQzFCOztBQUVELEFBQUEsS0FBSyxDQUFNLEVBQ1QsY0FBYyxFUGdCVCxNQUFLLENPaEJZLFVBQVUsR0FDakM7O0FBRUQsQUFBQSxLQUFLLENBQU0sRUFDVCxZQUFZLEVQWVAsTUFBSyxDT1pVLFVBQVUsR0FDL0I7O0FBRUQsQUFBQSxLQUFLLENBQU0sRUFDVCxhQUFhLEVQUVIsTUFBSyxDT1JXLFVBQVUsR0FDaEM7O0FBRUQsQUFBQSxLQUFLLENBQU0sRUFDVCxXQUFXLEVQSU4sTUFBSyxDT0pTLFVBQVUsR0FDOUI7O0FBRUQsQUFBQSxLQUFLLENBQU0sRUFDVCxZQUFZLEVQQVAsTUFBSyxDT0FVLFVBQVUsRUFDOUIsYUFBYSxFUERSLE1BQUssQ09DVyxVQUFVLEdBQ2hDOztBQUVELEFBQUEsS0FBSyxDQUFNLEVBQ1QsY0FBYyxFUExULE1BQUssQ09LWSxVQUFVLEVBQ2hDLFdBQVcsRVBOTixNQUFLLENPTVMsVUFBVSxHQUM5Qjs7QUE1QkQsQUFBQSxJQUFJLENBQU0sRUFDUixPQUFPLEVQcUJGLE1BQUssQ09yQkssVUFBVSxHQUMxQjs7QUFFRCxBQUFBLEtBQUssQ0FBTSxFQUNULGNBQWMsRVBpQlQsTUFBSyxDT2pCWSxVQUFVLEdBQ2pDOztBQUVELEFBQUEsS0FBSyxDQUFNLEVBQ1QsWUFBWSxFUGFQLE1BQUssQ09iVSxVQUFVLEdBQy9COztBQUVELEFBQUEsS0FBSyxDQUFNLEVBQ1QsYUFBYSxFUFNSLE1BQUssQ09UVyxVQUFVLEdBQ2hDOztBQUVELEFBQUEsS0FBSyxDQUFNLEVBQ1QsV0FBVyxFUEtOLE1BQUssQ09MUyxVQUFVLEdBQzlCOztBQUVELEFBQUEsS0FBSyxDQUFNLEVBQ1QsWUFBWSxFUENQLE1BQUssQ09EVSxVQUFVLEVBQzlCLGFBQWEsRVBBUixNQUFLLENPQVcsVUFBVSxHQUNoQzs7QUFFRCxBQUFBLEtBQUssQ0FBTSxFQUNULGNBQWMsRVBKVCxNQUFLLENPSVksVUFBVSxFQUNoQyxXQUFXLEVQTE4sTUFBSyxDT0tTLFVBQVUsR0FDOUI7O0E2QzlESCxBQUFBLFVBQVUsQ0FBQyxFQUNULGFBQWEsRXBEcUROLE1BQUssR29EcERiOztBQUVELEFBQUEsU0FBUyxDQUFDLEVBQ1IsYUFBYSxFQUFFLEdBQUcsR0FDbkI7O0FDTEQsQUFBQSxVQUFVLENBQUMsRUFDVCxVQUFVLEVBQUUsSUFBSSxHQUNqQjs7QUFFRCxBQUFBLFdBQVcsQ0FBQyxFQUNWLFVBQVUsRUFBRSxLQUFLLEdBQ2xCOztBQUVELEFBQUEsWUFBWSxDQUFDLEVBQ1gsVUFBVSxFQUFFLE1BQU0sR0FDbkI7O0FBRUQsQUFBQSxhQUFhLENBQUMsRUFDWixVQUFVLEVBQUUsT0FBTyxHQUNwQjs7QUFHRCxBQUFBLGVBQWUsQ0FBQyxFQUNkLGNBQWMsRUFBRSxTQUFTLEdBQzFCOztBQUVELEFBQUEsZUFBZSxDQUFDLEVBQ2QsY0FBYyxFQUFFLFNBQVMsR0FDMUI7O0FBRUQsQUFBQSxnQkFBZ0IsQ0FBQyxFQUNmLGNBQWMsRUFBRSxVQUFVLEdBQzNCOztBQUdELEFBQUEsWUFBWSxDQUFDLEVBQ1gsV0FBVyxFQUFFLE1BQU0sR0FDcEI7O0FBRUQsQUFBQSxVQUFVLENBQUMsRUFDVCxXQUFXLEVBQUUsSUFBSSxHQUNsQjs7QUFFRCxBQUFBLFlBQVksQ0FBQyxFQUNYLFVBQVUsRUFBRSxNQUFNLEdBQ25COztBQUVELEFBQUEsV0FBVyxDQUFDLEVBQ1YsU0FBUyxFQUFFLEtBQUssR0FDakI7O0FBR0QsQUFBQSxjQUFjLENBQUMsRTVDL0NiLFFBQVEsRUFBRSxNQUFNLEVBQ2hCLGFBQWEsRUFBRSxRQUFRLEVBQ3ZCLFdBQVcsRUFBRSxNQUFNLEc0QytDcEI7O0FBRUQsQUFBQSxVQUFVLENBQUMsRUFDVCxRQUFRLEVBQUUsTUFBTSxFQUNoQixhQUFhLEVBQUUsSUFBSSxFQUNuQixXQUFXLEVBQUUsTUFBTSxHQUNwQjs7QUFFRCxBQUFBLFdBQVcsQ0FBQyxFQUNWLE9BQU8sRUFBRSxJQUFJLEVBQ2IsVUFBVSxFQUFFLFVBQVUsRUFDdEIsU0FBUyxFQUFFLFVBQVUsR0FDdEIifQ== */ diff --git a/user/themes/test/css-compiled/spectre.min.css b/user/themes/test/css-compiled/spectre.min.css new file mode 100755 index 0000000..3ef16eb --- /dev/null +++ b/user/themes/test/css-compiled/spectre.min.css @@ -0,0 +1 @@ +/*! Spectre.css v0.5.8 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{overflow:visible;box-sizing:content-box;height:0}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:'SF Mono','Segoe UI Mono','Roboto Mono',Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}fieldset{margin:0;padding:0;border:0}legend{display:table;box-sizing:border-box;max-width:100%;padding:0;white-space:normal;color:inherit}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{font-size:20px;line-height:1.5;box-sizing:border-box;-webkit-tap-highlight-color:transparent}body{font-family:-apple-system,system-ui,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',sans-serif;font-size:.8rem;overflow-x:hidden;color:#50596c;background:#fff;text-rendering:optimizeLegibility}a{text-decoration:none;color:#3085ee;outline:0}a:focus{box-shadow:0 0 0 .1rem rgba(48,133,238,.2)}a.active,a:active,a:focus,a:hover{text-decoration:underline;color:#126bd9}a:visited{color:#5fa1f2}h1,h2,h3,h4,h5,h6{font-weight:500;line-height:1.2;margin-top:0;margin-bottom:.5em;color:inherit}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{cursor:help;text-decoration:none;border-bottom:.05rem dotted}kbd{font-size:.7rem;line-height:1.25;padding:.1rem .2rem;color:#fff;border-radius:.1rem;background:#454d5d}mark{padding:.05rem .1rem 0;color:#50596c;border-bottom:.05rem solid #ffd367;border-radius:.1rem;background:#ffe9b3}blockquote{margin-left:0;padding:.4rem .8rem;border-left:.1rem solid #e7e9ed}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,'Segoe UI',Roboto,'PingFang SC','Hiragino Sans GB','Microsoft YaHei','Helvetica Neue',sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,'Segoe UI',Roboto,'PingFang TC','Hiragino Sans CNS','Microsoft JhengHei','Helvetica Neue',sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,'Segoe UI',Roboto,'Hiragino Sans','Hiragino Kaku Gothic Pro','Yu Gothic',YuGothic,Meiryo,'Helvetica Neue',sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,'Segoe UI',Roboto,'Malgun Gothic','Helvetica Neue',sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{text-decoration:none;border-bottom:.05rem solid}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{width:100%;border-spacing:0;border-collapse:collapse;text-align:left}.table.table-striped tbody tr:nth-of-type(odd){background:#f8f9fa}.table tbody tr.active,.table.table-striped tbody tr.active{background:#f0f1f4}.table.table-hover tbody tr:hover{background:#f0f1f4}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{padding:.6rem .4rem;border-bottom:.05rem solid #e7e9ed}.table th{border-bottom-width:.1rem}.btn,.button{font-size:.8rem;line-height:1.2rem;display:inline-block;height:1.8rem;padding:.25rem .4rem;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;text-align:center;vertical-align:middle;white-space:nowrap;text-decoration:none;color:#3085ee;border:.05rem solid #3085ee;border-radius:.1rem;outline:0;background:#fff;-webkit-appearance:none;-moz-appearance:none;appearance:none}.btn:focus,.button:focus{box-shadow:0 0 0 .1rem rgba(48,133,238,.2)}.btn:focus,.btn:hover,.button:focus,.button:hover{text-decoration:none;border-color:#227ded;background:#e1edfd}.active.button,.btn.active,.btn:active,.button:active{text-decoration:none;color:#fff;border-color:#1370e3;background:#227ded}.active.loading.button::after,.btn.active.loading::after,.btn:active.loading::after,.button:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled],.button:disabled,.button[disabled],.disabled.button{cursor:default;pointer-events:none;opacity:.5}.btn-primary.button,.btn.btn-primary{color:#fff;border-color:#227ded;background:#3085ee}.btn-primary.button:focus,.btn-primary.button:hover,.btn.btn-primary:focus,.btn.btn-primary:hover{color:#fff;border-color:#1370e3;background:#1877ec}.btn-primary.active.button,.btn-primary.button:active,.btn.btn-primary.active,.btn.btn-primary:active{color:#fff;border-color:#126bd9;background:#1372e7}.btn-primary.loading.button::after,.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn-success.button,.btn.btn-success{color:#fff;border-color:#2faa3f;background:#32b643}.btn-success.button:focus,.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn-success.button:focus,.btn-success.button:hover,.btn.btn-success:focus,.btn.btn-success:hover{color:#fff;border-color:#2da23c;background:#30ae40}.btn-success.active.button,.btn-success.button:active,.btn.btn-success.active,.btn.btn-success:active{color:#fff;border-color:#278e34;background:#2a9a39}.btn-success.loading.button::after,.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn-error.button,.btn.btn-error{color:#fff;border-color:#d95000;background:#e85600}.btn-error.button:focus,.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn-error.button:focus,.btn-error.button:hover,.btn.btn-error:focus,.btn.btn-error:hover{color:#fff;border-color:#cf4d00;background:#de5200}.btn-error.active.button,.btn-error.button:active,.btn.btn-error.active,.btn.btn-error:active{color:#fff;border-color:#b54300;background:#c44900}.btn-error.loading.button::after,.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn-link.button,.btn.btn-link{color:#3085ee;border-color:transparent;background:0 0}.btn-link.active.button,.btn-link.button:active,.btn-link.button:focus,.btn-link.button:hover,.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#126bd9}.btn-sm.button,.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn-lg.button,.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn-block.button,.btn.btn-block{display:block;width:100%}.btn-action.button,.btn.btn-action{width:1.8rem;padding-right:0;padding-left:0}.btn-action.btn-sm.button,.btn.btn-action.btn-sm{width:1.4rem}.btn-action.btn-lg.button,.btn.btn-action.btn-lg{width:2rem}.btn-clear.button,.btn.btn-clear{line-height:.8rem;width:1rem;height:1rem;margin-right:-2px;margin-left:.2rem;padding:.1rem;text-decoration:none;opacity:1;color:currentColor;border:0;background:0 0}.btn-clear.button:focus,.btn-clear.button:hover,.btn.btn-clear:focus,.btn.btn-clear:hover{opacity:.95;background:rgba(248,249,250,.5)}.btn-clear.button::before,.btn.btn-clear::before{content:'\2715'}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn,.btn-group .button{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child),.btn-group .button:first-child:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child),.btn-group .button:not(:first-child):not(:last-child){margin-left:-.05rem;border-radius:0}.btn-group .btn:last-child:not(:first-child),.btn-group .button:last-child:not(:first-child){margin-left:-.05rem;border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .active.button,.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover,.btn-group .button:active,.btn-group .button:focus,.btn-group .button:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn,.btn-group.btn-group-block .button{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{line-height:1.2rem;display:block;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input,.search-input,[data-grav-field=array] input,[data-grav-field=array] textarea{font-size:.8rem;line-height:1.2rem;position:relative;display:block;width:100%;max-width:100%;height:1.8rem;padding:.25rem .4rem;transition:background .2s,border .2s,box-shadow .2s,color .2s;color:#50596c;border:.05rem solid #caced7;border-radius:.1rem;outline:0;background:#fff;background-image:none;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-input:focus,.search-input:focus,[data-grav-field=array] input:focus,[data-grav-field=array] textarea:focus{border-color:#3085ee;box-shadow:0 0 0 .1rem rgba(48,133,238,.2)}.form-input::-webkit-input-placeholder,.search-input::-webkit-input-placeholder,[data-grav-field=array] input::-webkit-input-placeholder,[data-grav-field=array] textarea::-webkit-input-placeholder{color:#acb3c2}.form-input:-ms-input-placeholder,.search-input:-ms-input-placeholder,[data-grav-field=array] input:-ms-input-placeholder,[data-grav-field=array] textarea:-ms-input-placeholder{color:#acb3c2}.form-input::-ms-input-placeholder,.search-input::-ms-input-placeholder,[data-grav-field=array] input::-ms-input-placeholder,[data-grav-field=array] textarea::-ms-input-placeholder{color:#acb3c2}.form-input::placeholder,.search-input::placeholder,[data-grav-field=array] input::placeholder,[data-grav-field=array] textarea::placeholder{color:#acb3c2}.form-input.input-sm,.input-sm.search-input,[data-grav-field=array] input.input-sm,[data-grav-field=array] textarea.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg,.input-lg.search-input,[data-grav-field=array] input.input-lg,[data-grav-field=array] textarea.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline,.input-inline.search-input,[data-grav-field=array] input.input-inline,[data-grav-field=array] textarea.input-inline{display:inline-block;width:auto;vertical-align:middle}.form-input[type=file],.search-input[type=file],[data-grav-field=array] input[type=file],[data-grav-field=array] textarea[type=file]{height:auto}[data-grav-field=array] textarea,[data-grav-field=array] textarea.input-lg,[data-grav-field=array] textarea.input-sm,textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm,textarea.input-lg.search-input,textarea.input-sm.search-input,textarea.search-input{height:auto}.form-input-hint{font-size:.7rem;margin-top:.2rem;color:#acb3c2}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{font-size:.8rem;line-height:1.2rem;width:100%;height:1.8rem;padding:.25rem .4rem;vertical-align:middle;color:inherit;border:.05rem solid #caced7;border-radius:.1rem;outline:0;background:#fff;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-select:focus{border-color:#3085ee;box-shadow:0 0 0 .1rem rgba(48,133,238,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){padding-right:1.2rem;background:#fff url('data:image/svg+xml;charset=utf8,%3Csvg%20xmlns=\'http://www.w3.org/2000/svg\'%20viewBox=\'0%200%204%205\'%3E%3Cpath%20fill=\'%23667189\'%20d=\'M2%200L0%202h4zm0%205L0%203h4z\'/%3E%3C/svg%3E') no-repeat right .35rem center/.4rem .5rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{position:absolute;z-index:2;top:50%;width:.8rem;height:.8rem;margin:0 .25rem;transform:translateY(-50%)}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input,.has-icon-left .search-input,.has-icon-left [data-grav-field=array] input,.has-icon-left [data-grav-field=array] textarea,[data-grav-field=array] .has-icon-left input,[data-grav-field=array] .has-icon-left textarea{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input,.has-icon-right .search-input,.has-icon-right [data-grav-field=array] input,.has-icon-right [data-grav-field=array] textarea,[data-grav-field=array] .has-icon-right input,[data-grav-field=array] .has-icon-right textarea{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{line-height:1.2rem;position:relative;display:block;min-height:1.4rem;margin:.2rem 0;padding:.1rem .4rem .1rem 1.2rem}.form-checkbox input,.form-radio input,.form-switch input{position:absolute;overflow:hidden;clip:rect(0,0,0,0);width:1px;height:1px;margin:-1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#3085ee;box-shadow:0 0 0 .1rem rgba(48,133,238,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{border-color:#3085ee;background:#3085ee}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{position:absolute;display:inline-block;cursor:pointer;transition:background .2s,border .2s,box-shadow .2s,color .2s;border:.05rem solid #caced7}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{top:.3rem;left:0;width:.8rem;height:.8rem;background:#fff}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#f0f1f4}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{position:absolute;top:50%;left:50%;width:6px;height:9px;margin-top:-6px;margin-left:-3px;content:'';transform:rotate(45deg);border:.1rem solid #fff;border-top-width:0;border-left-width:0;background-clip:padding-box}.form-checkbox input:indeterminate+.form-icon{border-color:#3085ee;background:#3085ee}.form-checkbox input:indeterminate+.form-icon::before{position:absolute;top:50%;left:50%;width:10px;height:2px;margin-top:-1px;margin-left:-5px;content:'';background:#fff}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{position:absolute;top:50%;left:50%;width:6px;height:6px;content:'';transform:translate(-50%,-50%);border-radius:50%;background:#fff}.form-switch{padding-left:2rem}.form-switch .form-icon{top:.25rem;left:0;width:1.6rem;height:.9rem;border-radius:.45rem;background:#acb3c2;background-clip:padding-box}.form-switch .form-icon::before{position:absolute;top:0;left:0;display:block;width:.8rem;height:.8rem;content:'';transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;border-radius:50%;background:#fff}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f8f9fa}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap;border:.05rem solid #caced7;border-radius:.1rem;background:#f8f9fa}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select,.input-group .search-input,.input-group [data-grav-field=array] input,.input-group [data-grav-field=array] textarea,[data-grav-field=array] .input-group input,[data-grav-field=array] .input-group textarea{width:1%;-ms-flex:1 1 auto;flex:1 1 auto}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child),.input-group .search-input:first-child:not(:last-child),.input-group [data-grav-field=array] input:first-child:not(:last-child),.input-group [data-grav-field=array] textarea:first-child:not(:last-child),[data-grav-field=array] .input-group input:first-child:not(:last-child),[data-grav-field=array] .input-group textarea:first-child:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child),.input-group .search-input:not(:first-child):not(:last-child),.input-group [data-grav-field=array] input:not(:first-child):not(:last-child),.input-group [data-grav-field=array] textarea:not(:first-child):not(:last-child),[data-grav-field=array] .input-group input:not(:first-child):not(:last-child),[data-grav-field=array] .input-group textarea:not(:first-child):not(:last-child){margin-left:-.05rem;border-radius:0}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child),.input-group .search-input:last-child:not(:first-child),.input-group [data-grav-field=array] input:last-child:not(:first-child),.input-group [data-grav-field=array] textarea:last-child:not(:first-child),[data-grav-field=array] .input-group input:last-child:not(:first-child),[data-grav-field=array] .input-group textarea:last-child:not(:first-child){margin-left:-.05rem;border-top-left-radius:0;border-bottom-left-radius:0}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus,.input-group .search-input:focus,.input-group [data-grav-field=array] input:focus,.input-group [data-grav-field=array] textarea:focus,[data-grav-field=array] .input-group input:focus,[data-grav-field=array] .input-group textarea:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select,.has-success .search-input,.has-success [data-grav-field=array] input,.has-success [data-grav-field=array] textarea,.is-success.search-input,[data-grav-field=array] .has-success input,[data-grav-field=array] .has-success textarea,[data-grav-field=array] input.is-success,[data-grav-field=array] textarea.is-success{border-color:#32b643;background:#f9fdfa}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus,.has-success .search-input:focus,.has-success [data-grav-field=array] input:focus,.has-success [data-grav-field=array] textarea:focus,.is-success.search-input:focus,[data-grav-field=array] .has-success input:focus,[data-grav-field=array] .has-success textarea:focus,[data-grav-field=array] input.is-success:focus,[data-grav-field=array] textarea.is-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select,.has-error .search-input,.has-error [data-grav-field=array] input,.has-error [data-grav-field=array] textarea,.is-error.search-input,[data-grav-field=array] .has-error input,[data-grav-field=array] .has-error textarea,[data-grav-field=array] input.is-error,[data-grav-field=array] textarea.is-error{border-color:#e85600;background:#fffaf7}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus,.has-error .search-input:focus,.has-error [data-grav-field=array] input:focus,.has-error [data-grav-field=array] textarea:focus,.is-error.search-input:focus,[data-grav-field=array] .has-error input:focus,[data-grav-field=array] .has-error textarea:focus,[data-grav-field=array] input.is-error:focus,[data-grav-field=array] textarea.is-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{border-color:#e85600;background:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{border-color:#e85600;background:#e85600}.form-input:not(:placeholder-shown):invalid,.search-input:not(:placeholder-shown):invalid,[data-grav-field=array] input:not(:placeholder-shown):invalid,[data-grav-field=array] textarea:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid:focus,.search-input:not(:placeholder-shown):invalid:focus,[data-grav-field=array] input:not(:placeholder-shown):invalid:focus,[data-grav-field=array] textarea:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid+.form-input-hint,.search-input:not(:placeholder-shown):invalid+.form-input-hint,[data-grav-field=array] input:not(:placeholder-shown):invalid+.form-input-hint,[data-grav-field=array] textarea:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.disabled.search-input,.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled,.search-input:disabled,[data-grav-field=array] input.disabled,[data-grav-field=array] input:disabled,[data-grav-field=array] textarea.disabled,[data-grav-field=array] textarea:disabled{cursor:not-allowed;opacity:.5;background-color:#f0f1f4}.form-input[readonly],.search-input[readonly],[data-grav-field=array] input[readonly],[data-grav-field=array] textarea[readonly]{background-color:#f8f9fa}input.disabled+.form-icon,input:disabled+.form-icon{cursor:not-allowed;opacity:.5;background:#f0f1f4}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{line-height:1.25;display:inline-block;padding:.1rem .2rem;color:#5b657a;border-radius:.1rem;background:#f0f1f4}.label.label-rounded{padding-right:.4rem;padding-left:.4rem;border-radius:5rem}.label.label-primary{color:#fff;background:#3085ee}.label.label-secondary{color:#3085ee;background:#e1edfd}.label.label-success{color:#fff;background:#32b643}.label.label-warning{color:#fff;background:#ffb700}.label.label-error{color:#fff;background:#e85600}code{font-size:85%;line-height:1.25;padding:.1rem .2rem;color:#d73e48;border-radius:.1rem;background:#fcf2f2}.code{position:relative;color:#50596c;border-radius:.1rem}.code::before{font-size:.7rem;position:absolute;top:.1rem;right:.4rem;content:attr(data-lang);color:#acb3c2}.code code{line-height:1.5;display:block;overflow-x:auto;width:100%;padding:1rem;color:inherit;background:#f8f9fa}.img-responsive{display:block;max-width:100%;height:auto}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{position:relative;display:block;overflow:hidden;width:100%;padding:0}.video-responsive::before{display:block;padding-bottom:56.25%;content:''}.video-responsive embed,.video-responsive iframe,.video-responsive object{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;border:0}video.video-responsive{max-width:100%;height:auto}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{margin-top:.4rem;color:#667189}.container{width:100%;margin-right:auto;margin-left:auto;padding-right:.4rem;padding-left:.4rem}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.columns{display:-ms-flexbox;display:flex;margin-right:-.4rem;margin-left:-.4rem;-ms-flex-wrap:wrap;flex-wrap:wrap}.columns.col-gapless{margin-right:0;margin-left:0}.columns.col-gapless>.column{padding-right:0;padding-left:0}.columns.col-oneline{overflow-x:auto;-ms-flex-wrap:nowrap;flex-wrap:nowrap}.column{max-width:100%;padding-right:.4rem;padding-left:.4rem;-ms-flex:1;flex:1}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{width:auto;max-width:none;-ms-flex:0 0 auto;flex:0 0 auto}.col-mx-auto{margin-right:auto;margin-left:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;flex-direction:column;padding-top:4rem;padding-bottom:4rem;-ms-flex-direction:column;-ms-flex-pack:justify;justify-content:space-between}.hero.hero-sm{padding-top:2rem;padding-bottom:2rem}.hero.hero-lg{padding-top:8rem;padding-bottom:8rem}.hero .hero-body{padding:.4rem}.navbar{display:-ms-flexbox;display:flex;-ms-flex-align:stretch;align-items:stretch;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:justify;justify-content:space-between}.navbar .navbar-section{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex:1 0 0;flex:1 0 0}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex:0 0 auto;flex:0 0 auto}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header .icon,.accordion[open] .accordion-header .icon{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{overflow:hidden;max-height:0;margin-bottom:.4rem;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{font-size:.8rem;font-weight:300;line-height:1.25;position:relative;display:inline-block;width:1.6rem;height:1.6rem;margin:0;vertical-align:middle;color:rgba(255,255,255,.85);border-radius:50%;background:#3085ee}.avatar.avatar-xs{font-size:.4rem;width:.8rem;height:.8rem}.avatar.avatar-sm{font-size:.6rem;width:1.2rem;height:1.2rem}.avatar.avatar-lg{font-size:1.2rem;width:2.4rem;height:2.4rem}.avatar.avatar-xl{font-size:1.6rem;width:3.2rem;height:3.2rem}.avatar img{position:relative;z-index:1;width:100%;height:100%;border-radius:50%}.avatar .avatar-icon,.avatar .avatar-presence{position:absolute;z-index:2;right:14.64%;bottom:14.64%;width:50%;height:50%;padding:.1rem;transform:translate(50%,50%);background:#fff}.avatar .avatar-presence{width:.5em;height:.5em;border-radius:50%;background:#acb3c2;box-shadow:0 0 0 .1rem #fff}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{position:absolute;z-index:1;top:50%;left:50%;content:attr(data-initial);transform:translate(-50%,-50%);color:currentColor}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{display:inline-block;content:attr(data-badge);transform:translate(-.05rem,-.5rem);color:#fff;border-radius:.5rem;background:#3085ee;background-clip:padding-box;box-shadow:0 0 0 .1rem #fff}.badge[data-badge]::after{font-size:.7rem;line-height:1;min-width:.9rem;height:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge='']::after{width:6px;min-width:6px;height:6px;padding:0}.badge.btn::after,.badge.button::after{position:absolute;top:0;right:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;z-index:100;top:14.64%;right:14.64%;transform:translate(50%,-50%)}.breadcrumb{margin:.2rem 0;padding:.2rem 0;list-style:none}.breadcrumb .breadcrumb-item{display:inline-block;margin:0;padding:.2rem 0;color:#667189}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#667189}.breadcrumb .breadcrumb-item:not(:first-child)::before{padding-right:.4rem;content:'/';color:#667189}.bar{display:-ms-flexbox;display:flex;width:100%;height:.8rem;border-radius:.1rem;background:#f0f1f4;-ms-flex-wrap:nowrap;flex-wrap:nowrap}.bar.bar-sm{height:.2rem}.bar .bar-item{font-size:.7rem;line-height:.8rem;position:relative;display:block;width:0;height:100%;text-align:center;color:#fff;background:#3085ee;-ms-flex-negative:0;flex-shrink:0}.bar .bar-item:first-child{border-top-left-radius:.1rem;border-bottom-left-radius:.1rem}.bar .bar-item:last-child{border-top-right-radius:.1rem;border-bottom-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{position:relative;height:.1rem;margin:.4rem 0}.bar-slider .bar-item{position:absolute;left:0;padding:0}.bar-slider .bar-item:not(:last-child):first-child{z-index:1;background:#f0f1f4}.bar-slider .bar-slider-btn{position:absolute;top:50%;right:0;width:.6rem;height:.6rem;padding:0;transform:translate(50%,-50%);border:0;border-radius:50%;background:#3085ee}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #3085ee}.card{display:-ms-flexbox;display:flex;flex-direction:column;border:.05rem solid #e7e9ed;border-radius:.1rem;background:#fff;-ms-flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-right-radius:.1rem;border-bottom-left-radius:.1rem}.chip{font-size:90%;line-height:.8rem;display:-ms-inline-flexbox;display:inline-flex;overflow:hidden;max-width:320px;height:1.2rem;margin:.1rem;padding:.2rem .4rem;vertical-align:middle;white-space:nowrap;text-decoration:none;text-overflow:ellipsis;border-radius:5rem;background:#f0f1f4;-ms-flex-align:center;align-items:center}.chip.active{color:#fff;background:#3085ee}.chip .avatar{margin-right:.2rem;margin-left:-.4rem}.chip .btn-clear{transform:scale(.75);border-radius:50%}.dropdown{position:relative;display:inline-block}.dropdown .menu{position:absolute;top:100%;left:0;display:none;overflow-y:auto;max-height:50vh;animation:slide-down .15s ease 1}.dropdown.dropdown-right .menu{right:0;left:auto}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-top-right-radius:.1rem;border-bottom-right-radius:.1rem}.empty{padding:3.2rem 1.6rem;text-align:center;color:#667189;border-radius:.1rem;background:#f8f9fa}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{z-index:300;min-width:180px;margin:0;padding:.4rem;list-style:none;transform:translateY(.2rem);border-radius:.1rem;background:#fff;box-shadow:0 .05rem .2rem rgba(69,77,93,.3)}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{position:relative;margin-top:0;padding:0 .4rem;text-decoration:none}.menu .menu-item>a{display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none;color:inherit;border-radius:.1rem}.menu .menu-item>a:focus,.menu .menu-item>a:hover{color:#3085ee;background:#e1edfd}.menu .menu-item>a.active,.menu .menu-item>a:active{color:#3085ee;background:#e1edfd}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{position:absolute;top:0;right:0;display:-ms-flexbox;display:flex;height:100%;-ms-flex-align:center;align-items:center}.menu .menu-badge .label{margin-right:.4rem}.modal{position:fixed;top:0;right:0;bottom:0;left:0;display:none;overflow:hidden;padding:.4rem;opacity:0;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.modal.active,.modal:target{z-index:400;display:-ms-flexbox;display:flex;opacity:1}.modal.active .modal-overlay,.modal:target .modal-overlay{position:absolute;top:0;right:0;bottom:0;left:0;display:block;cursor:default;background:rgba(248,249,250,.75)}.modal.active .modal-container,.modal:target .modal-container{z-index:1;animation:slide-down .2s ease 1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{max-width:960px;box-shadow:none}.modal-container{display:-ms-flexbox;display:flex;flex-direction:column;width:100%;max-width:640px;max-height:75vh;padding:0 .8rem;border-radius:.1rem;background:#fff;box-shadow:0 .2rem .5rem rgba(69,77,93,.3);-ms-flex-direction:column}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{padding:.8rem;color:#454d5d}.modal-container .modal-body{position:relative;overflow-y:auto;padding:.8rem}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;flex-direction:column;margin:.2rem 0;list-style:none;-ms-flex-direction:column}.nav .nav-item a{padding:.2rem .4rem;text-decoration:none;color:#667189}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#3085ee}.nav .nav-item.active>a{font-weight:700;color:#50596c}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#3085ee}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;margin:.2rem 0;padding:.2rem 0;list-style:none}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{display:inline-block;padding:.2rem .4rem;text-decoration:none;border-radius:.1rem}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#3085ee}.pagination .page-item.disabled a{cursor:default;pointer-events:none;opacity:.5}.pagination .page-item.active a{color:#fff;background:#3085ee}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{display:-ms-flexbox;display:flex;flex-direction:column;border:.05rem solid #e7e9ed;border-radius:.1rem;-ms-flex-direction:column}.panel .panel-footer,.panel .panel-header{padding:.8rem;-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{overflow-y:auto;padding:0 .8rem;-ms-flex:1 1 auto;flex:1 1 auto}.popover{position:relative;display:inline-block}.popover .popover-container{position:absolute;z-index:300;top:0;left:50%;width:320px;padding:.4rem;transition:transform .2s;transform:translate(-50%,-50%) scale(0);opacity:0}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;transform:translate(-50%,-100%) scale(1);opacity:1}.popover.popover-right .popover-container{top:50%;left:100%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{top:100%;left:50%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{top:50%;left:0}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(69,77,93,.3)}.step{display:-ms-flexbox;display:flex;width:100%;margin:.2rem 0;list-style:none;-ms-flex-wrap:nowrap;flex-wrap:nowrap}.step .step-item{position:relative;min-height:1rem;margin-top:0;text-align:center;-ms-flex:1 1 0;flex:1 1 0}.step .step-item:not(:first-child)::before{position:absolute;top:9px;left:-50%;width:100%;height:2px;content:'';background:#3085ee}.step .step-item a{display:inline-block;padding:20px 10px 0;text-decoration:none;color:#3085ee}.step .step-item a::before{position:absolute;z-index:1;top:.2rem;left:50%;display:block;width:.6rem;height:.6rem;content:'';transform:translateX(-50%);border:.1rem solid #fff;border-radius:50%;background:#3085ee}.step .step-item.active a::before{border:.1rem solid #3085ee;background:#fff}.step .step-item.active~.step-item::before{background:#e7e9ed}.step .step-item.active~.step-item a{color:#acb3c2}.step .step-item.active~.step-item a::before{background:#e7e9ed}.tab{display:-ms-flexbox;display:flex;margin:.2rem 0 .15rem 0;list-style:none;border-bottom:.05rem solid #e7e9ed;-ms-flex-align:center;align-items:center;-ms-flex-wrap:wrap;flex-wrap:wrap}.tab .tab-item{margin-top:0}.tab .tab-item a{display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none;color:inherit;border-bottom:.1rem solid transparent}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#3085ee}.tab .tab-item a.active,.tab .tab-item.active a{color:#3085ee;border-bottom-color:#3085ee}.tab .tab-item.tab-action{text-align:right;-ms-flex:1 0 auto;flex:1 0 auto}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{text-align:center;-ms-flex:1 0 0;flex:1 0 0}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;top:.1rem;right:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{display:-ms-flexbox;display:flex;-ms-flex-line-pack:justify;align-content:space-between;-ms-flex-align:start;align-items:flex-start}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{-ms-flex-align:center;align-items:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{overflow:hidden;margin-bottom:0;white-space:nowrap;text-overflow:ellipsis}.toast{display:block;width:100%;padding:.4rem;color:#fff;border:.05rem solid #454d5d;border-color:#454d5d;border-radius:.1rem;background:rgba(69,77,93,.95)}.toast.toast-primary{border-color:#3085ee;background:rgba(48,133,238,.95)}.toast.toast-success{border-color:#32b643;background:rgba(50,182,67,.95)}.toast.toast-warning{border-color:#ffb700;background:rgba(255,183,0,.95)}.toast.toast-error{border-color:#e85600;background:rgba(232,86,0,.95)}.toast a{text-decoration:underline;color:#fff}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{font-size:.7rem;position:absolute;z-index:300;bottom:100%;left:50%;display:block;overflow:hidden;max-width:320px;padding:.2rem .4rem;content:attr(data-tooltip);transition:opacity .2s,transform .2s;transform:translate(-50%,.4rem);white-space:pre;text-overflow:ellipsis;pointer-events:none;opacity:0;color:#fff;border-radius:.1rem;background:rgba(69,77,93,.95)}.tooltip:focus::after,.tooltip:hover::after{transform:translate(-50%,-.2rem);opacity:1}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{top:100%;bottom:auto;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{right:100%;bottom:50%;left:auto;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{transform:translateY(-1.6rem);opacity:0}100%{transform:translateY(0);opacity:1}}.text-primary{color:#3085ee!important}a.text-primary:focus,a.text-primary:hover{color:#1877ec}a.text-primary:visited{color:#4893f0}.text-secondary{color:#d3e5fb!important}a.text-secondary:focus,a.text-secondary:hover{color:#bbd7f9}a.text-secondary:visited{color:#eaf3fd}.text-gray{color:#acb3c2!important}a.text-gray:focus,a.text-gray:hover{color:#9ea6b7}a.text-gray:visited{color:#bbc1cd}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#50596c!important}a.text-dark:focus,a.text-dark:hover{color:#454d5d}a.text-dark:visited{color:#5b657a}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{color:#fff;background:#3085ee!important}.bg-secondary{background:#e1edfd!important}.bg-dark{color:#fff;background:#454d5d!important}.bg-gray{background:#f8f9fa!important}.bg-success{color:#fff;background:#32b643!important}.bg-warning{color:#fff;background:#ffb700!important}.bg-error{color:#fff;background:#e85600!important}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{font-size:0;line-height:0;color:transparent;border:0;background:0 0;text-shadow:none}.text-assistive{position:absolute;overflow:hidden;clip:rect(0,0,0,0);width:1px;height:1px;margin:-1px;padding:0;border:0}.divider,.divider-vert{position:relative;display:block}.divider-vert[data-content]::after,.divider[data-content]::after{font-size:.7rem;display:inline-block;padding:0 .4rem;content:attr(data-content);transform:translateY(-.65rem);color:#acb3c2;background:#fff}.divider{height:.05rem;margin:.4rem 0;border-top:.05rem solid #fefefe}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{position:absolute;top:.4rem;bottom:.4rem;left:50%;display:block;content:'';transform:translateX(-50%);border-left:.05rem solid #e7e9ed}.divider-vert[data-content]::after{position:absolute;top:50%;left:50%;padding:.2rem 0;transform:translate(-50%,-50%)}.loading{position:relative;min-height:.8rem;pointer-events:none;color:transparent!important}.loading::after{position:absolute;z-index:1;top:50%;left:50%;display:block;width:.8rem;height:.8rem;margin-top:-.4rem;margin-left:-.4rem;content:'';animation:loading .5s infinite linear;border:.1rem solid #3085ee;border-top-color:transparent;border-right-color:transparent;border-radius:50%}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{width:1.6rem;height:1.6rem;margin-top:-.8rem;margin-left:-.8rem}.clearfix::after{display:table;clear:both;content:''}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-right:auto;margin-left:auto}.flex-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-right:0!important;margin-left:0!important}.my-0{margin-top:0!important;margin-bottom:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-right:.2rem!important;margin-left:.2rem!important}.my-1{margin-top:.2rem!important;margin-bottom:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-right:.4rem!important;margin-left:.4rem!important}.my-2{margin-top:.4rem!important;margin-bottom:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-right:0!important;padding-left:0!important}.py-0{padding-top:0!important;padding-bottom:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-right:.2rem!important;padding-left:.2rem!important}.py-1{padding-top:.2rem!important;padding-bottom:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-right:.4rem!important;padding-left:.4rem!important}.py-2{padding-top:.4rem!important;padding-bottom:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-ellipsis{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.text-clip{overflow:hidden;white-space:nowrap;text-overflow:clip}.text-break{word-wrap:break-word;word-break:break-word;-webkit-hyphens:auto;hyphens:auto;-ms-hyphens:auto} \ No newline at end of file diff --git a/user/themes/test/css-compiled/theme.css b/user/themes/test/css-compiled/theme.css new file mode 100644 index 0000000..28c68a9 --- /dev/null +++ b/user/themes/test/css-compiled/theme.css @@ -0,0 +1,406 @@ +html { height: 100%; } + +#body-wrapper .container { padding: 2rem 0 2rem; } + +.header-fixed #body-wrapper { padding-top: 4rem; } + +.header-fixed .hero + #start > #body-wrapper { padding-top: 0; } + +section.section { padding-left: 1rem; padding-right: 1rem; position: relative; } + +.overlay-light, .overlay-dark, .overlay-light-gradient, .overlay-dark-gradient { z-index: 0; } + +.hero { display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; -ms-flex-pack: center; justify-content: center; padding-top: 6rem; padding-bottom: 7rem; background-size: cover; background-position: center; } + +.hero h1 { color: #242931; font-size: 4rem; } + +.hero h2 { color: rgba(36, 41, 49, 0.8); font-size: 2.5rem; } + +.hero.hero-fullscreen { min-height: 100vh; } + +.hero.hero-large { min-height: 500px; } + +.hero.hero-medium { min-height: 400px; } + +.hero.hero-small { min-height: 110px; } + +.hero.hero-tiny { min-height: 8rem; } + +.header-fixed .hero { background-position: 50% 0; } + +@media (max-width: 840px) { .hero h1 { font-size: 3rem; } + .hero h2 { font-size: 1.75rem; } } + +@media (max-width: 600px) { .hero h1 { font-size: 2rem; } + .hero h2 { font-size: 1.25rem; } } + +.hero.text-light h1 { color: #fff; } + +.hero.text-light h2 { color: rgba(255, 255, 255, 0.8); } + +.hero p { font-size: .9rem; font-weight: 300; } + +.hero #to-start { display: inline-block; position: absolute; bottom: 10px; font-size: 2rem; cursor: pointer; } + +.image-overlay { position: absolute; top: 0; bottom: 0; left: 0; right: 0; z-index: -1; } + +.overlay-light .image-overlay { background: rgba(255, 255, 255, 0.4); } + +.overlay-light-gradient .image-overlay { background: linear-gradient(to bottom, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.2)); } + +.overlay-dark .image-overlay { background: rgba(0, 0, 0, 0.4); } + +.overlay-dark-gradient .image-overlay { background: linear-gradient(to bottom, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.2)); } + +html { font-size: 16px; } + +@media screen and (min-width: 480px) { html { font-size: calc(16px + 4 * ((100vw - 480px) / 800)); } } + +@media screen and (min-width: 1280px) { html { font-size: 20px; } } + +h1, h2, h3, h4, h5, h6 { margin-top: 2rem; font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif; color: #3a414e; } + +h1, .h1 { font-size: 3rem; } + +h2, .h2 { font-size: 1.8rem; } + +h6, .h6 { font-weight: 400; } + +.title-center h1, .title-center h2 { text-align: center; } + +.title-h1h2 h1 { font-weight: 100; margin-bottom: 0; line-height: 1.1; } + +.title-h1h2 h1 strong, .title-h1h2 h1 bold { font-weight: 400; } + +.title-h1h2 h1 + h2 { line-height: 1.1; margin-top: 0; } + +.title-h1h2 h1 + h2, .title-center h1 + h2 { margin-bottom: 50px; font-weight: 700; } + +a:focus { outline: none !important; box-shadow: none !important; } + +img { max-width: 100%; } + +.table table { border-spacing: 0; border-collapse: collapse; width: 100%; } + +pre code, pre.xdebug-var-dump { background: #fafafa; display: block; padding: 1rem !important; line-height: 1.5; color: inherit; border-radius: 2px; overflow-x: auto; } + +pre[class*="language-"] code { border-radius: inherit; padding: 0 !important; overflow-x: initial; } + +pre code:not(.hljs):not([class*="language-"]) { background: #f8f8f8; } + +i.fa.fa-heart.pulse, i.fa.fa-heart-o.pulse { color: #920; } + +b, strong { font-weight: 700; } + +.heavy { font-weight: 700; } + +.light { font-weight: 200; } + +.text-light { color: rgba(255, 255, 255, 0.8); } + +.text-light h1, .text-light h2, .text-light h3, .text-light h4, .text-light h5, .text-light h6 { color: rgba(255, 255, 255, 0.9); } + +#error { text-align: center; position: relative; margin-top: 5rem; } + +#error .icon { font-size: 50px; } + +#messages { margin-bottom: 1rem; } + +#messages .icon { font-size: 1rem; } + +ul, ol { margin-left: 1.6rem; } + +ul ul, ul ol, ol ul, ol ol { margin-left: 1.6rem; } + +ul { list-style: disc outside; } + +ol { list-style: decimal outside; } + +.notices { margin: 1.5rem 0; } + +.notices p { margin: 1rem 0; } + +form { /** Reset some defaults for Quark Theme **/ } + +form .button-wrapper { margin-top: 0.75rem; margin-bottom: 1rem; } + +form span.required { color: #e85600; font-weight: 700; font-size: 1.2rem; } + +form .form-input[type=range] { -webkit-appearance: slider-horizontal; -moz-appearance: slider-horizontal; appearance: slider-horizontal; } + +form .form-input[type=range]:focus { box-shadow: none; border: none; } + +form .form-group:not(.form-field-toggleable) .checkboxes { display: inherit; } + +form .form-group:not(.form-field-toggleable) .checkboxes label { display: inherit; padding: 0.1rem 0.4rem 0.1rem 1.2rem; margin: inherit; } + +form .form-group:not(.form-field-toggleable) .checkboxes label:before { display: none; } + +#grav-login > form { margin: 2rem auto 0; max-width: 350px; } + +#grav-login .form-label { display: none; } + +#grav-login .form-data { margin: 1rem 0; } + +#grav-login .form-input { text-align: center; } + +#grav-login .button-wrapper { text-align: right; } + +#grav-login .button-wrapper .form-data.rememberme { margin: 0; float: left; } + +#grav-login .login-form button[type="submit"] { background: #3085EE; border-color: #227ded; color: #fff; } + +#grav-login .login-form button[type="submit"]:focus, #grav-login .login-form button[type="submit"]:hover { background: #1877ec; border-color: #1370e3; color: #fff; } + +#grav-login .login-form button[type="submit"]:active, #grav-login .login-form button[type="submit"].active { background: #1372e7; border-color: #126bd9; color: #fff; } + +#grav-login .twofa-form button[type="submit"]:first-child { background: #3085EE; border-color: #227ded; color: #fff; float: right; margin-left: 4px; } + +#grav-login .twofa-form button[type="submit"]:first-child:focus, #grav-login .twofa-form button[type="submit"]:first-child:hover { background: #1877ec; border-color: #1370e3; color: #fff; } + +#grav-login .twofa-form button[type="submit"]:first-child:active, #grav-login .twofa-form button[type="submit"]:first-child.active { background: #1372e7; border-color: #126bd9; color: #fff; } + +.mobile-container { position: absolute; top: 40%; left: 0; margin: 0 auto; z-index: 2; } + +.mobile-logo svg, .mobile-logo img { height: 42px; margin-top: .7rem; margin-left: 1.4rem; } + +.mobile-logo svg path, .mobile-logo img path { fill: #fff; } + +.mobile-menu { display: none; top: 0; right: 0; z-index: 3; } + +.header-fixed .mobile-menu { position: fixed; } + +@media (max-width: 840px) { .mobile-menu { display: block; } } + +.mobile-menu .button_container { position: absolute; top: 1.3rem; right: 1rem; height: 24px; width: 28px; cursor: pointer; z-index: 100; transition: opacity .25s ease, top 0.5s ease; } + +.mobile-menu .button_container:hover { opacity: .7; } + +.mobile-menu .button_container.active { position: fixed; } + +.mobile-menu .button_container.active .top { transform: translateY(8px) translateX(0) rotate(45deg); background: #FFF; } + +.mobile-menu .button_container.active .middle { opacity: 0; background: #FFF; } + +.mobile-menu .button_container.active .bottom { transform: translateY(-8px) translateX(0) rotate(-45deg); background: #FFF; } + +.mobile-menu .button_container span { background: #3085EE; border: none; height: 4px; width: 100%; position: absolute; top: 0; left: 0; transition: all .35s ease; cursor: pointer; } + +.mobile-menu .button_container span:nth-of-type(2) { top: 8px; } + +.mobile-menu .button_container span:nth-of-type(3) { top: 16px; } + +.overlay { position: fixed; background: #000; top: 0; left: 0; width: 100%; height: 0%; opacity: 0; visibility: hidden; transition: opacity .35s, visibility .35s, height .35s; } + +.overlay.open { opacity: .95; visibility: visible; height: 100%; } + +.overlay nav { position: relative; margin: 0 auto; text-align: center; } + +.overlay-menu { height: calc(100% - 90px); overflow-y: scroll; } + +.overlay-menu > .tree { text-align: left; } + +.treemenu.treemenu-root { margin: 1rem; } + +.treemenu li { list-style: none; margin: 0 0 1px; padding: 5px 0; line-height: 1.2rem; background: rgba(102, 113, 137, 0.1); } + +.treemenu li a { display: block; margin-left: 1.2rem; font-size: 1rem; } + +.treemenu li a:hover, .treemenu li a:focus, .treemenu li a.active { color: #3e8def !important; text-decoration: none; } + +.treemenu ul { margin: 0 0 0 1rem; } + +.treemenu .toggler { cursor: pointer; vertical-align: top; font-size: 1.1rem; line-height: 1rem; padding-left: 5px; float: left; } + +.treemenu .toggler:before { display: inline-block; margin-right: 2pt; } + +.treemenu li.tree-empty > .toggler { opacity: 0.3; cursor: default; } + +.treemenu li.tree-empty > .toggler:before { content: "\2022"; } + +.treemenu li.tree-closed > .toggler:before { content: "+"; } + +.treemenu li.tree-opened > .toggler:before { content: "\2212"; } + +.mobile-nav-open { overflow-y: hidden; } + +.default-animation, #header, #header .navbar-section, #header .logo svg, #header .logo img, .modular-features.small .feature-icon i, .modular-features .feature-icon { transition: all 0.5s ease; } + +.pulse { animation-name: pulse_animation; animation-duration: 2000ms; transform-origin: 70% 70%; animation-iteration-count: infinite; animation-timing-function: linear; } + +@keyframes pulse_animation { 0% { transform: scale(1); } + 30% { transform: scale(1); } + 40% { transform: scale(1.08); } + 50% { transform: scale(1); } + 60% { transform: scale(1); } + 70% { transform: scale(1.05); } + 80% { transform: scale(1); } + 100% { transform: scale(1); } } + +#header { width: 100%; height: 4rem; border-bottom: 1px solid rgba(172, 179, 194, 0.2); font-size: 0.7rem; font-weight: 700; background: #fff; color: #454d5d; } + +#header a { color: #454d5d; } + +#header .logo svg path { fill: #222; } + +.header-dark #header:not(.scrolled) { background: #222; color: #fff; } + +.header-dark #header:not(.scrolled) a { color: rgba(255, 255, 255, 0.7) !important; } + +.header-dark #header:not(.scrolled) a.active { color: #fff !important; } + +.header-dark #header:not(.scrolled) .dropmenu ul ul a { color: #454d5d !important; } + +.header-dark #header:not(.scrolled) .logo svg path { fill: #fff; } + +.header-dark.header-transparent #header:not(.scrolled) { background: rgba(0, 0, 0, 0.05); } + +.header-transparent #header:not(.scrolled) { background: rgba(255, 255, 255, 0.05); } + +#header .navbar-section { height: 4rem; } + +@media (max-width: 840px) { #header .navbar-section { margin-right: 2rem; } } + +@media (max-width: 840px) { #header .navbar-section.desktop-menu { display: none; } } + +#header .logo svg, #header .logo img { height: 42px; display: inherit; } + +.header-fixed #header { position: fixed; top: 0; z-index: 2; } + +body.header-fixed.header-animated #header.scrolled { height: 2.3rem; } + +body.header-fixed.header-animated #header.scrolled .navbar-section { height: 2.3rem; } + +body.header-fixed.header-animated #header.scrolled .logo svg, body.header-fixed.header-animated #header.scrolled .logo img { height: 28px; } + +body.header-fixed.header-animated #header.scrolled ~ .mobile-menu .button_container { top: 0.5rem; } + +.login-status-wrapper { white-space: nowrap; } + +body.sticky-footer { height: 100%; min-height: 100vh; display: -ms-flexbox; display: flex; -ms-flex-direction: column; flex-direction: column; } + +body.sticky-footer #page-wrapper { -ms-flex: 1 0 auto; flex: 1 0 auto; } + +#footer { color: #acb3c2; padding: 1rem 1rem 0; text-align: center; } + +@media (max-width: 840px) { .dropmenu { display: none; } } + +.dropmenu ul { white-space: nowrap; margin: 0; display: -ms-flexbox; display: flex; } + +.dropmenu ul li { position: relative; margin: 0; } + +.dropmenu ul li a { text-decoration: none; padding: 7px 30px 7px 20px; display: block; } + +.dropmenu ul li a:hover, .dropmenu ul li a:focus, .dropmenu ul li a.active { color: #3085EE !important; } + +.dropmenu ul li a:before { content: '\f107'; font-family: 'FontAwesome'; display: inline-block; vertical-align: middle; float: right; margin-right: -20px; } + +.dropmenu ul li a:only-child { padding-right: 20px; } + +.dropmenu ul li a:only-child:before { content: ''; } + +.dropmenu ul li:hover > ul { display: block; visibility: visible; } + +.dropmenu ul ul li a:before { content: '\f105'; } + +.dropmenu ul ul { position: absolute; top: 100%; list-style: none; background: #fff; box-shadow: 0 3px 5px rgba(0, 0, 0, 0.1); visibility: hidden; } + +.dropmenu ul ul ul { position: absolute; left: 100%; top: 0; } + +.dropmenu > ul > li { display: inline-block; } + +.dropmenu.animated ul li { transition: background .7s, color 0.5s; } + +.dropmenu.animated ul li:hover > ul { opacity: 1; transform: translateY(0); } + +.dropmenu.animated ul ul { transition: transform .3s, opacity .5s; opacity: 0; transform: translateY(-10px); } + +/** Extra columns spacing **/ +.extra-spacing:not(.col-12), :not(.col12) > .e-content { padding-right: 1rem; } + +@media (max-width: 840px) { .extra-spacing:not(.col-12), :not(.col12) > .e-content { padding-right: 0; } } + +/** Breadcrumbs styling **/ +#breadcrumbs { padding-left: 0; display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; margin-top: -1rem; margin-bottom: 1rem; } + +#breadcrumbs i { display: none; } + +#breadcrumbs span, #breadcrumbs a { padding: 0 0.5rem; } + +#breadcrumbs span:first-child, #breadcrumbs a:first-child { padding-left: 0; } + +#breadcrumbs span:not(:first-child)::before, #breadcrumbs a:not(:first-child)::before { color: #e7e9ed; content: "/"; padding-right: 1rem; } + +/** Blog Listing **/ +.blog-listing .bricklayer-column { padding-left: 0px; padding-right: 25px; } + +.blog-listing .card { margin-bottom: 25px; border: 0; box-shadow: 0 10px 45px -9px rgba(0, 0, 0, 0.1); } + +.blog-listing .card-footer { text-align: right; } + +.blog-listing .blog-date { font-size: 13px; } + +/** Blog Item **/ +.content-title { margin-bottom: 2rem; } + +.content-title h2 { margin-bottom: 0.5rem; } + +.label { font-size: 12px; text-transform: uppercase; } + +/** Pagination **/ +ul.pagination { -ms-flex-pack: center; justify-content: center; } + +.prev-next { margin-top: 4rem; } + +/** Sidebar specific tweaks **/ +#sidebar ul.related-pages { box-shadow: none; padding: 0; z-index: 1; } + +#sidebar ul.related-pages li { border-bottom: 1px solid #e7e9ed; } + +#sidebar ul.related-pages li:last-child { border-bottom: 0; } + +#sidebar ul.archives { list-style: none; margin-left: 0; } + +#sidebar ul.archives .label { vertical-align: text-top; } + +.modular-hero #to-start { bottom: 3.5rem; } + +.modular-features { text-align: center; } + +.modular-features.offset-box .frame-box { margin: -3rem -1.4rem 3rem; padding: 1rem 1rem; background: #fff; box-shadow: 0 0 75px 0 rgba(69, 77, 93, 0.1); } + +.modular-features.small .columns { margin-top: -1rem; } + +.modular-features.small .column:hover .feature-icon i { color: #3085EE; } + +.modular-features.small .feature-icon { display: block; -ms-flex-pack: left; justify-content: left; } + +.modular-features.small .feature-icon i { position: relative; display: inherit; font-size: 70px; margin: 0 auto 1rem; transform: none; left: auto; top: auto; color: #acb3c2; } + +.modular-features.small .feature-icon h6 { text-transform: none; } + +.modular-features .frame-box { padding: 3rem 0; } + +.modular-features .frame-box > p { max-width: 600px; margin-left: auto; margin-right: auto; } + +.modular-features .column { padding: 1rem; } + +.modular-features .column:hover .feature-icon { color: #acb3c2; } + +.modular-features .column:hover .feature-icon h6 { color: #3085EE; } + +.modular-features .column:hover .feature-content { color: #667189; } + +.modular-features .feature-icon { font-size: 130px; height: 100px; color: #e7e9ed; display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; -ms-flex-pack: center; justify-content: center; position: relative; margin: 1rem 0; } + +.modular-features .feature-icon i { position: absolute; left: 50%; top: 50%; transform: translateX(-50%) translateY(-50%); } + +.modular-features .feature-icon h6 { background: #fff; line-height: 1; z-index: 1; text-transform: uppercase; font-weight: 600; margin: 0; display: block; color: #667189; } + +.modular-features .feature-content { color: #acb3c2; } + +.modular-text { padding-top: 4rem; padding-bottom: 4rem; } + +.modular-text .columns.left { -ms-flex-direction: row-reverse; flex-direction: row-reverse; } + +/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGhlbWUuY3NzIiwic291cmNlcyI6WyJ0aGVtZS5zY3NzIiwidGhlbWUvX3ZhcmlhYmxlcy5zY3NzIiwic3BlY3RyZS9fdmFyaWFibGVzLnNjc3MiLCJzcGVjdHJlL19taXhpbnMuc2NzcyIsInNwZWN0cmUvbWl4aW5zL19hdmF0YXIuc2NzcyIsInNwZWN0cmUvbWl4aW5zL19idXR0b24uc2NzcyIsInNwZWN0cmUvbWl4aW5zL19jbGVhcmZpeC5zY3NzIiwic3BlY3RyZS9taXhpbnMvX2NvbG9yLnNjc3MiLCJzcGVjdHJlL21peGlucy9fbGFiZWwuc2NzcyIsInNwZWN0cmUvbWl4aW5zL19wb3NpdGlvbi5zY3NzIiwic3BlY3RyZS9taXhpbnMvX3NoYWRvdy5zY3NzIiwic3BlY3RyZS9taXhpbnMvX3RleHQuc2NzcyIsInNwZWN0cmUvbWl4aW5zL190b2FzdC5zY3NzIiwidGhlbWUvX2ZvbnRzLnNjc3MiLCJ0aGVtZS9fbWl4aW5zLnNjc3MiLCJ0aGVtZS9fZnJhbWV3b3JrLnNjc3MiLCJ0aGVtZS9fdHlwb2dyYXBoeS5zY3NzIiwidGhlbWUvX2Zvcm1zLnNjc3MiLCJ0aGVtZS9fbW9iaWxlLnNjc3MiLCJ0aGVtZS9fYW5pbWF0aW9uLnNjc3MiLCJ0aGVtZS9faGVhZGVyLnNjc3MiLCJ0aGVtZS9fZm9vdGVyLnNjc3MiLCJ0aGVtZS9fbWVudS5zY3NzIiwidGhlbWUvX2Jsb2cuc2NzcyIsInRoZW1lL19vbmVwYWdlLnNjc3MiXSwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29yZSB2YXJpYWJsZXMgYW5kIG1peGluc1xuQGltcG9ydCAndGhlbWUvdmFyaWFibGVzJztcbkBpbXBvcnQgJ3NwZWN0cmUvdmFyaWFibGVzJztcbkBpbXBvcnQgJ3NwZWN0cmUvbWl4aW5zJztcblxuQGltcG9ydCAndGhlbWUvZm9udHMnO1xuQGltcG9ydCAndGhlbWUvbWl4aW5zJztcbkBpbXBvcnQgJ3RoZW1lL2ZyYW1ld29yayc7XG5AaW1wb3J0ICd0aGVtZS90eXBvZ3JhcGh5JztcbkBpbXBvcnQgJ3RoZW1lL2Zvcm1zJztcbkBpbXBvcnQgJ3RoZW1lL21vYmlsZSc7XG5AaW1wb3J0ICd0aGVtZS9hbmltYXRpb24nO1xuXG5AaW1wb3J0ICd0aGVtZS9oZWFkZXInO1xuQGltcG9ydCAndGhlbWUvZm9vdGVyJztcbkBpbXBvcnQgJ3RoZW1lL21lbnUnO1xuXG4vLyBFeHRyYSBTa2VsZXRvbiBTdHlsaW5nXG5AaW1wb3J0ICd0aGVtZS9ibG9nJztcbkBpbXBvcnQgJ3RoZW1lL29uZXBhZ2UnO1xuXG4iLCIvLyBTcGVjdHJlIE92ZXJyaWRlc1xuJHByaW1hcnktY29sb3I6ICMzMDg1RUUgIWRlZmF1bHQ7XG4kZGFyay1jb2xvcjogIzQ1NGQ1ZCAhZGVmYXVsdDtcbiRsaWdodC1jb2xvcjogI2ZmZiAhZGVmYXVsdDtcbiRncmF5LWNvbG9yOiBsaWdodGVuKCRkYXJrLWNvbG9yLCA0MCUpICFkZWZhdWx0O1xuJGdyYXktY29sb3ItZGFyazogZGFya2VuKCRncmF5LWNvbG9yLCAyNSUpICFkZWZhdWx0O1xuJGJvcmRlci1jb2xvcjogbGlnaHRlbigkZGFyay1jb2xvciwgNjAlKSAhZGVmYXVsdDtcbiRiZy1jb2xvcjogbGlnaHRlbigkZGFyay1jb2xvciwgNjYlKSAhZGVmYXVsdDtcbiRib2R5LWZvbnQtY29sb3I6IGxpZ2h0ZW4oJGRhcmstY29sb3IsIDUlKSAhZGVmYXVsdDtcblxuLy8gTGF5b3V0XG4kaG9yaXotcGFkZGluZzogMXJlbTtcbiR2ZXJ0LXBhZGRpbmc6IDJyZW0gMCAycmVtO1xuXG4vLyBGb250c1xuJG1pbi1yZXNwb25zaXZlLWZvbnQtc2l6ZTogMTZweDtcblxuLy8gSGVhZGVyXG4kaGVhZGVyLWhlaWdodC1sYXJnZTogNHJlbTtcbiRoZWFkZXItaGVpZ2h0LXNtYWxsOiAyLjNyZW07XG4kaGVhZGVyLWNvbG9yLWRhcms6ICMyMjI7XG4kaGVhZGVyLXRleHQtbGlnaHQ6ICRsaWdodC1jb2xvcjtcbiRoZWFkZXItdGV4dC1kYXJrOiBkYXJrZW4oJGRhcmstY29sb3IsIDE1JSk7XG5cbi8vIERyb3Bkb3duIE1lbnVcbiRkcm9wbWVudS1iZzogJGxpZ2h0LWNvbG9yO1xuJGRyb3BtZW51LWhvdmVyLXRleHQ6ICRwcmltYXJ5LWNvbG9yO1xuJGRyb3BtZW51LWhvcml6LXBhZGRpbmc6IDIwcHg7XG4kZHJvcG1lbnUtdmVydC1wYWRkaW5nOiA3cHg7XG4kZHJvcG1lbnUtY2hpbGQtcGFkZGluZzogMTBweDtcbiRkcm9wbWVudS1zaGFkb3c6IDAgM3B4IDVweCByZ2JhKDAsIDAsIDAsIDAuMSk7XG5cbi8vIE1vYmlsZSBNZW51XG4kbW9iaWxlLWNvbG9yLW1haW46ICRwcmltYXJ5LWNvbG9yO1xuJG1vYmlsZS1jb2xvci1hY3RpdmU6ICNGRkY7XG4kbW9iaWxlLWNvbG9yLWxpbms6ICNGRkY7XG4kbW9iaWxlLWJ1dHRvbi1oZWlnaHQ6IDI0cHg7XG4kbW9iaWxlLWJ1dHRvbi13aWR0aDogMjhweDsiLCIvLyBDb3JlIHZhcmlhYmxlc1xuJHZlcnNpb246IFwiMC41LjhcIjtcblxuLy8gQ29yZSBmZWF0dXJlc1xuJHJ0bDogZmFsc2UgIWRlZmF1bHQ7XG5cbi8vIENvcmUgY29sb3JzXG4kcHJpbWFyeS1jb2xvcjogIzU3NTVkOSAhZGVmYXVsdDtcbiRwcmltYXJ5LWNvbG9yLWRhcms6IGRhcmtlbigkcHJpbWFyeS1jb2xvciwgMyUpICFkZWZhdWx0O1xuJHByaW1hcnktY29sb3ItbGlnaHQ6IGxpZ2h0ZW4oJHByaW1hcnktY29sb3IsIDMlKSAhZGVmYXVsdDtcbiRzZWNvbmRhcnktY29sb3I6IGxpZ2h0ZW4oJHByaW1hcnktY29sb3IsIDM3LjUlKSAhZGVmYXVsdDtcbiRzZWNvbmRhcnktY29sb3ItZGFyazogZGFya2VuKCRzZWNvbmRhcnktY29sb3IsIDMlKSAhZGVmYXVsdDtcbiRzZWNvbmRhcnktY29sb3ItbGlnaHQ6IGxpZ2h0ZW4oJHNlY29uZGFyeS1jb2xvciwgMyUpICFkZWZhdWx0O1xuXG4vLyBHcmF5IGNvbG9yc1xuJGRhcmstY29sb3I6ICMzMDM3NDIgIWRlZmF1bHQ7XG4kbGlnaHQtY29sb3I6ICNmZmYgIWRlZmF1bHQ7XG4kZ3JheS1jb2xvcjogbGlnaHRlbigkZGFyay1jb2xvciwgNTUlKSAhZGVmYXVsdDtcbiRncmF5LWNvbG9yLWRhcms6IGRhcmtlbigkZ3JheS1jb2xvciwgMzAlKSAhZGVmYXVsdDtcbiRncmF5LWNvbG9yLWxpZ2h0OiBsaWdodGVuKCRncmF5LWNvbG9yLCAyMCUpICFkZWZhdWx0O1xuXG4kYm9yZGVyLWNvbG9yOiBsaWdodGVuKCRkYXJrLWNvbG9yLCA2NSUpICFkZWZhdWx0O1xuJGJvcmRlci1jb2xvci1kYXJrOiBkYXJrZW4oJGJvcmRlci1jb2xvciwgMTAlKSAhZGVmYXVsdDtcbiRib3JkZXItY29sb3ItbGlnaHQ6IGxpZ2h0ZW4oJGJvcmRlci1jb2xvciwgOCUpICFkZWZhdWx0O1xuJGJnLWNvbG9yOiBsaWdodGVuKCRkYXJrLWNvbG9yLCA3NSUpICFkZWZhdWx0O1xuJGJnLWNvbG9yLWRhcms6IGRhcmtlbigkYmctY29sb3IsIDMlKSAhZGVmYXVsdDtcbiRiZy1jb2xvci1saWdodDogJGxpZ2h0LWNvbG9yICFkZWZhdWx0O1xuXG4vLyBDb250cm9sIGNvbG9yc1xuJHN1Y2Nlc3MtY29sb3I6ICMzMmI2NDMgIWRlZmF1bHQ7XG4kd2FybmluZy1jb2xvcjogI2ZmYjcwMCAhZGVmYXVsdDtcbiRlcnJvci1jb2xvcjogI2U4NTYwMCAhZGVmYXVsdDtcblxuLy8gT3RoZXIgY29sb3JzXG4kY29kZS1jb2xvcjogI2Q3M2U0OCAhZGVmYXVsdDtcbiRoaWdobGlnaHQtY29sb3I6ICNmZmU5YjMgIWRlZmF1bHQ7XG4kYm9keS1iZzogJGJnLWNvbG9yLWxpZ2h0ICFkZWZhdWx0O1xuJGJvZHktZm9udC1jb2xvcjogbGlnaHRlbigkZGFyay1jb2xvciwgNSUpICFkZWZhdWx0O1xuJGxpbmstY29sb3I6ICRwcmltYXJ5LWNvbG9yICFkZWZhdWx0O1xuJGxpbmstY29sb3ItZGFyazogZGFya2VuKCRsaW5rLWNvbG9yLCAxMCUpICFkZWZhdWx0O1xuJGxpbmstY29sb3ItbGlnaHQ6IGxpZ2h0ZW4oJGxpbmstY29sb3IsIDEwJSkgIWRlZmF1bHQ7XG5cbi8vIEZvbnRzXG4vLyBDcmVkaXQ6IGh0dHBzOi8vd3d3LnNtYXNoaW5nbWFnYXppbmUuY29tLzIwMTUvMTEvdXNpbmctc3lzdGVtLXVpLWZvbnRzLXByYWN0aWNhbC1ndWlkZS9cbiRiYXNlLWZvbnQtZmFtaWx5OiAtYXBwbGUtc3lzdGVtLCBzeXN0ZW0tdWksIEJsaW5rTWFjU3lzdGVtRm9udCwgXCJTZWdvZSBVSVwiLCBSb2JvdG8gIWRlZmF1bHQ7XG4kbW9uby1mb250LWZhbWlseTogXCJTRiBNb25vXCIsIFwiU2Vnb2UgVUkgTW9ub1wiLCBcIlJvYm90byBNb25vXCIsIE1lbmxvLCBDb3VyaWVyLCBtb25vc3BhY2UgIWRlZmF1bHQ7XG4kZmFsbGJhY2stZm9udC1mYW1pbHk6IFwiSGVsdmV0aWNhIE5ldWVcIiwgc2Fucy1zZXJpZiAhZGVmYXVsdDtcbiRjamstemgtaGFucy1mb250LWZhbWlseTogJGJhc2UtZm9udC1mYW1pbHksIFwiUGluZ0ZhbmcgU0NcIiwgXCJIaXJhZ2lubyBTYW5zIEdCXCIsIFwiTWljcm9zb2Z0IFlhSGVpXCIsICRmYWxsYmFjay1mb250LWZhbWlseSAhZGVmYXVsdDtcbiRjamstemgtaGFudC1mb250LWZhbWlseTogJGJhc2UtZm9udC1mYW1pbHksIFwiUGluZ0ZhbmcgVENcIiwgXCJIaXJhZ2lubyBTYW5zIENOU1wiLCBcIk1pY3Jvc29mdCBKaGVuZ0hlaVwiLCAkZmFsbGJhY2stZm9udC1mYW1pbHkgIWRlZmF1bHQ7XG4kY2prLWpwLWZvbnQtZmFtaWx5OiAkYmFzZS1mb250LWZhbWlseSwgXCJIaXJhZ2lubyBTYW5zXCIsIFwiSGlyYWdpbm8gS2FrdSBHb3RoaWMgUHJvXCIsIFwiWXUgR290aGljXCIsIFl1R290aGljLCBNZWlyeW8sICRmYWxsYmFjay1mb250LWZhbWlseSAhZGVmYXVsdDtcbiRjamsta28tZm9udC1mYW1pbHk6ICRiYXNlLWZvbnQtZmFtaWx5LCBcIk1hbGd1biBHb3RoaWNcIiwgJGZhbGxiYWNrLWZvbnQtZmFtaWx5ICFkZWZhdWx0O1xuJGJvZHktZm9udC1mYW1pbHk6ICRiYXNlLWZvbnQtZmFtaWx5LCAkZmFsbGJhY2stZm9udC1mYW1pbHkgIWRlZmF1bHQ7XG5cbi8vIFVuaXQgc2l6ZXNcbiR1bml0LW86IC4wNXJlbSAhZGVmYXVsdDtcbiR1bml0LWg6IC4xcmVtICFkZWZhdWx0O1xuJHVuaXQtMTogLjJyZW0gIWRlZmF1bHQ7XG4kdW5pdC0yOiAuNHJlbSAhZGVmYXVsdDtcbiR1bml0LTM6IC42cmVtICFkZWZhdWx0O1xuJHVuaXQtNDogLjhyZW0gIWRlZmF1bHQ7XG4kdW5pdC01OiAxcmVtICFkZWZhdWx0O1xuJHVuaXQtNjogMS4ycmVtICFkZWZhdWx0O1xuJHVuaXQtNzogMS40cmVtICFkZWZhdWx0O1xuJHVuaXQtODogMS42cmVtICFkZWZhdWx0O1xuJHVuaXQtOTogMS44cmVtICFkZWZhdWx0O1xuJHVuaXQtMTA6IDJyZW0gIWRlZmF1bHQ7XG4kdW5pdC0xMjogMi40cmVtICFkZWZhdWx0O1xuJHVuaXQtMTY6IDMuMnJlbSAhZGVmYXVsdDtcblxuLy8gRm9udCBzaXplc1xuJGh0bWwtZm9udC1zaXplOiAyMHB4ICFkZWZhdWx0O1xuJGh0bWwtbGluZS1oZWlnaHQ6IDEuNSAhZGVmYXVsdDtcbiRmb250LXNpemU6IC44cmVtICFkZWZhdWx0O1xuJGZvbnQtc2l6ZS1zbTogLjdyZW0gIWRlZmF1bHQ7XG4kZm9udC1zaXplLWxnOiAuOXJlbSAhZGVmYXVsdDtcbiRsaW5lLWhlaWdodDogMS4ycmVtICFkZWZhdWx0O1xuXG4vLyBTaXplc1xuJGxheW91dC1zcGFjaW5nOiAkdW5pdC0yICFkZWZhdWx0O1xuJGxheW91dC1zcGFjaW5nLXNtOiAkdW5pdC0xICFkZWZhdWx0O1xuJGxheW91dC1zcGFjaW5nLWxnOiAkdW5pdC00ICFkZWZhdWx0O1xuJGJvcmRlci1yYWRpdXM6ICR1bml0LWggIWRlZmF1bHQ7XG4kYm9yZGVyLXdpZHRoOiAkdW5pdC1vICFkZWZhdWx0O1xuJGJvcmRlci13aWR0aC1sZzogJHVuaXQtaCAhZGVmYXVsdDtcbiRjb250cm9sLXNpemU6ICR1bml0LTkgIWRlZmF1bHQ7XG4kY29udHJvbC1zaXplLXNtOiAkdW5pdC03ICFkZWZhdWx0O1xuJGNvbnRyb2wtc2l6ZS1sZzogJHVuaXQtMTAgIWRlZmF1bHQ7XG4kY29udHJvbC1wYWRkaW5nLXg6ICR1bml0LTIgIWRlZmF1bHQ7XG4kY29udHJvbC1wYWRkaW5nLXgtc206ICR1bml0LTIgKiAuNzUgIWRlZmF1bHQ7XG4kY29udHJvbC1wYWRkaW5nLXgtbGc6ICR1bml0LTIgKiAxLjUgIWRlZmF1bHQ7XG4kY29udHJvbC1wYWRkaW5nLXk6ICgkY29udHJvbC1zaXplIC0gJGxpbmUtaGVpZ2h0KSAvIDIgLSAkYm9yZGVyLXdpZHRoICFkZWZhdWx0O1xuJGNvbnRyb2wtcGFkZGluZy15LXNtOiAoJGNvbnRyb2wtc2l6ZS1zbSAtICRsaW5lLWhlaWdodCkgLyAyIC0gJGJvcmRlci13aWR0aCAhZGVmYXVsdDtcbiRjb250cm9sLXBhZGRpbmcteS1sZzogKCRjb250cm9sLXNpemUtbGcgLSAkbGluZS1oZWlnaHQpIC8gMiAtICRib3JkZXItd2lkdGggIWRlZmF1bHQ7XG4kY29udHJvbC1pY29uLXNpemU6IC44cmVtICFkZWZhdWx0O1xuXG4kY29udHJvbC13aWR0aC14czogMTgwcHggIWRlZmF1bHQ7XG4kY29udHJvbC13aWR0aC1zbTogMzIwcHggIWRlZmF1bHQ7XG4kY29udHJvbC13aWR0aC1tZDogNjQwcHggIWRlZmF1bHQ7XG4kY29udHJvbC13aWR0aC1sZzogOTYwcHggIWRlZmF1bHQ7XG4kY29udHJvbC13aWR0aC14bDogMTI4MHB4ICFkZWZhdWx0O1xuXG4vLyBSZXNwb25zaXZlIGJyZWFrcG9pbnRzXG4kc2l6ZS14czogNDgwcHggIWRlZmF1bHQ7XG4kc2l6ZS1zbTogNjAwcHggIWRlZmF1bHQ7XG4kc2l6ZS1tZDogODQwcHggIWRlZmF1bHQ7XG4kc2l6ZS1sZzogOTYwcHggIWRlZmF1bHQ7XG4kc2l6ZS14bDogMTI4MHB4ICFkZWZhdWx0O1xuJHNpemUtMng6IDE0NDBweCAhZGVmYXVsdDtcblxuJHJlc3BvbnNpdmUtYnJlYWtwb2ludDogJHNpemUteHMgIWRlZmF1bHQ7XG5cbi8vIFotaW5kZXhcbiR6aW5kZXgtMDogMSAhZGVmYXVsdDtcbiR6aW5kZXgtMTogMTAwICFkZWZhdWx0O1xuJHppbmRleC0yOiAyMDAgIWRlZmF1bHQ7XG4kemluZGV4LTM6IDMwMCAhZGVmYXVsdDtcbiR6aW5kZXgtNDogNDAwICFkZWZhdWx0O1xuIiwiLy8gTWl4aW5zXG5AaW1wb3J0IFwibWl4aW5zL2F2YXRhclwiO1xuQGltcG9ydCBcIm1peGlucy9idXR0b25cIjtcbkBpbXBvcnQgXCJtaXhpbnMvY2xlYXJmaXhcIjtcbkBpbXBvcnQgXCJtaXhpbnMvY29sb3JcIjtcbkBpbXBvcnQgXCJtaXhpbnMvbGFiZWxcIjtcbkBpbXBvcnQgXCJtaXhpbnMvcG9zaXRpb25cIjtcbkBpbXBvcnQgXCJtaXhpbnMvc2hhZG93XCI7XG5AaW1wb3J0IFwibWl4aW5zL3RleHRcIjtcbkBpbXBvcnQgXCJtaXhpbnMvdG9hc3RcIjsiLCIvLyBBdmF0YXIgbWl4aW5cbkBtaXhpbiBhdmF0YXItYmFzZSgkc2l6ZTogJHVuaXQtOCkge1xuICBmb250LXNpemU6ICRzaXplIC8gMjtcbiAgaGVpZ2h0OiAkc2l6ZTtcbiAgd2lkdGg6ICRzaXplO1xufVxuIiwiLy8gQnV0dG9uIHZhcmlhbnQgbWl4aW5cbkBtaXhpbiBidXR0b24tdmFyaWFudCgkY29sb3I6ICRwcmltYXJ5LWNvbG9yKSB7XG4gIGJhY2tncm91bmQ6ICRjb2xvcjtcbiAgYm9yZGVyLWNvbG9yOiBkYXJrZW4oJGNvbG9yLCAzJSk7XG4gIGNvbG9yOiAkbGlnaHQtY29sb3I7XG4gICY6Zm9jdXMge1xuICAgIEBpbmNsdWRlIGNvbnRyb2wtc2hhZG93KCRjb2xvcik7XG4gIH1cbiAgJjpmb2N1cyxcbiAgJjpob3ZlciB7XG4gICAgYmFja2dyb3VuZDogZGFya2VuKCRjb2xvciwgMiUpO1xuICAgIGJvcmRlci1jb2xvcjogZGFya2VuKCRjb2xvciwgNSUpO1xuICAgIGNvbG9yOiAkbGlnaHQtY29sb3I7XG4gIH1cbiAgJjphY3RpdmUsXG4gICYuYWN0aXZlIHtcbiAgICBiYWNrZ3JvdW5kOiBkYXJrZW4oJGNvbG9yLCA3JSk7XG4gICAgYm9yZGVyLWNvbG9yOiBkYXJrZW4oJGNvbG9yLCAxMCUpO1xuICAgIGNvbG9yOiAkbGlnaHQtY29sb3I7XG4gIH1cbiAgJi5sb2FkaW5nIHtcbiAgICAmOjphZnRlciB7XG4gICAgICBib3JkZXItYm90dG9tLWNvbG9yOiAkbGlnaHQtY29sb3I7XG4gICAgICBib3JkZXItbGVmdC1jb2xvcjogJGxpZ2h0LWNvbG9yO1xuICAgIH1cbiAgfVxufVxuXG5AbWl4aW4gYnV0dG9uLW91dGxpbmUtdmFyaWFudCgkY29sb3I6ICRwcmltYXJ5LWNvbG9yKSB7XG4gIGJhY2tncm91bmQ6ICRsaWdodC1jb2xvcjtcbiAgYm9yZGVyLWNvbG9yOiAkY29sb3I7XG4gIGNvbG9yOiAkY29sb3I7XG4gICY6Zm9jdXMge1xuICAgIEBpbmNsdWRlIGNvbnRyb2wtc2hhZG93KCRjb2xvcik7XG4gIH1cbiAgJjpmb2N1cyxcbiAgJjpob3ZlciB7XG4gICAgYmFja2dyb3VuZDogbGlnaHRlbigkY29sb3IsIDUwJSk7XG4gICAgYm9yZGVyLWNvbG9yOiBkYXJrZW4oJGNvbG9yLCAyJSk7XG4gICAgY29sb3I6ICRjb2xvcjtcbiAgfVxuICAmOmFjdGl2ZSxcbiAgJi5hY3RpdmUge1xuICAgIGJhY2tncm91bmQ6ICRjb2xvcjtcbiAgICBib3JkZXItY29sb3I6IGRhcmtlbigkY29sb3IsIDUlKTtcbiAgICBjb2xvcjogJGxpZ2h0LWNvbG9yO1xuICB9XG4gICYubG9hZGluZyB7XG4gICAgJjo6YWZ0ZXIge1xuICAgICAgYm9yZGVyLWJvdHRvbS1jb2xvcjogJGNvbG9yO1xuICAgICAgYm9yZGVyLWxlZnQtY29sb3I6ICRjb2xvcjtcbiAgICB9XG4gIH1cbn1cbiIsIi8vIENsZWFyZml4IG1peGluXG5AbWl4aW4gY2xlYXJmaXgoKSB7XG4gICY6OmFmdGVyIHtcbiAgICBjbGVhcjogYm90aDtcbiAgICBjb250ZW50OiBcIlwiO1xuICAgIGRpc3BsYXk6IHRhYmxlO1xuICB9XG59XG4iLCIvLyBCYWNrZ3JvdW5kIGNvbG9yIHV0aWxpdHkgbWl4aW5cbkBtaXhpbiBiZy1jb2xvci12YXJpYW50KCRuYW1lOiBcIi5iZy1wcmltYXJ5XCIsICRjb2xvcjogJHByaW1hcnktY29sb3IpIHtcbiAgI3skbmFtZX0ge1xuICAgIGJhY2tncm91bmQ6ICRjb2xvciAhaW1wb3J0YW50O1xuXG4gICAgQGlmIChsaWdodG5lc3MoJGNvbG9yKSA8IDYwKSB7XG4gICAgICBjb2xvcjogJGxpZ2h0LWNvbG9yO1xuICAgIH1cbiAgfVxufVxuXG4vLyBUZXh0IGNvbG9yIHV0aWxpdHkgbWl4aW5cbkBtaXhpbiB0ZXh0LWNvbG9yLXZhcmlhbnQoJG5hbWU6IFwiLnRleHQtcHJpbWFyeVwiLCAkY29sb3I6ICRwcmltYXJ5LWNvbG9yKSB7XG4gICN7JG5hbWV9IHtcbiAgICBjb2xvcjogJGNvbG9yICFpbXBvcnRhbnQ7XG4gIH1cblxuICBhI3skbmFtZX0ge1xuICAgICY6Zm9jdXMsXG4gICAgJjpob3ZlciB7XG4gICAgICBjb2xvcjogZGFya2VuKCRjb2xvciwgNSUpO1xuICAgIH1cbiAgICAmOnZpc2l0ZWQge1xuICAgICAgY29sb3I6IGxpZ2h0ZW4oJGNvbG9yLCA1JSk7XG4gICAgfVxuICB9XG59XG4iLCIvLyBMYWJlbCBiYXNlIHN0eWxlXG5AbWl4aW4gbGFiZWwtYmFzZSgpIHtcbiAgYm9yZGVyLXJhZGl1czogJGJvcmRlci1yYWRpdXM7XG4gIGxpbmUtaGVpZ2h0OiAxLjI1O1xuICBwYWRkaW5nOiAuMXJlbSAuMnJlbTtcbn1cblxuQG1peGluIGxhYmVsLXZhcmlhbnQoJGNvbG9yOiAkbGlnaHQtY29sb3IsICRiZy1jb2xvcjogJHByaW1hcnktY29sb3IpIHtcbiAgYmFja2dyb3VuZDogJGJnLWNvbG9yO1xuICBjb2xvcjogJGNvbG9yO1xufVxuIiwiLy8gTWFyZ2luIHV0aWxpdHkgbWl4aW5cbkBtaXhpbiBtYXJnaW4tdmFyaWFudCgkaWQ6IDEsICRzaXplOiAkdW5pdC0xKSB7XG4gIC5tLSN7JGlkfSB7XG4gICAgbWFyZ2luOiAkc2l6ZSAhaW1wb3J0YW50O1xuICB9XG5cbiAgLm1iLSN7JGlkfSB7XG4gICAgbWFyZ2luLWJvdHRvbTogJHNpemUgIWltcG9ydGFudDtcbiAgfVxuXG4gIC5tbC0jeyRpZH0ge1xuICAgIG1hcmdpbi1sZWZ0OiAkc2l6ZSAhaW1wb3J0YW50O1xuICB9XG5cbiAgLm1yLSN7JGlkfSB7XG4gICAgbWFyZ2luLXJpZ2h0OiAkc2l6ZSAhaW1wb3J0YW50O1xuICB9XG5cbiAgLm10LSN7JGlkfSB7XG4gICAgbWFyZ2luLXRvcDogJHNpemUgIWltcG9ydGFudDtcbiAgfVxuXG4gIC5teC0jeyRpZH0ge1xuICAgIG1hcmdpbi1sZWZ0OiAkc2l6ZSAhaW1wb3J0YW50O1xuICAgIG1hcmdpbi1yaWdodDogJHNpemUgIWltcG9ydGFudDtcbiAgfVxuXG4gIC5teS0jeyRpZH0ge1xuICAgIG1hcmdpbi1ib3R0b206ICRzaXplICFpbXBvcnRhbnQ7XG4gICAgbWFyZ2luLXRvcDogJHNpemUgIWltcG9ydGFudDtcbiAgfVxufVxuXG4vLyBQYWRkaW5nIHV0aWxpdHkgbWl4aW5cbkBtaXhpbiBwYWRkaW5nLXZhcmlhbnQoJGlkOiAxLCAkc2l6ZTogJHVuaXQtMSkge1xuICAucC0jeyRpZH0ge1xuICAgIHBhZGRpbmc6ICRzaXplICFpbXBvcnRhbnQ7XG4gIH1cblxuICAucGItI3skaWR9IHtcbiAgICBwYWRkaW5nLWJvdHRvbTogJHNpemUgIWltcG9ydGFudDtcbiAgfVxuXG4gIC5wbC0jeyRpZH0ge1xuICAgIHBhZGRpbmctbGVmdDogJHNpemUgIWltcG9ydGFudDtcbiAgfVxuXG4gIC5wci0jeyRpZH0ge1xuICAgIHBhZGRpbmctcmlnaHQ6ICRzaXplICFpbXBvcnRhbnQ7XG4gIH1cblxuICAucHQtI3skaWR9IHtcbiAgICBwYWRkaW5nLXRvcDogJHNpemUgIWltcG9ydGFudDtcbiAgfVxuXG4gIC5weC0jeyRpZH0ge1xuICAgIHBhZGRpbmctbGVmdDogJHNpemUgIWltcG9ydGFudDtcbiAgICBwYWRkaW5nLXJpZ2h0OiAkc2l6ZSAhaW1wb3J0YW50O1xuICB9XG4gIFxuICAucHktI3skaWR9IHtcbiAgICBwYWRkaW5nLWJvdHRvbTogJHNpemUgIWltcG9ydGFudDtcbiAgICBwYWRkaW5nLXRvcDogJHNpemUgIWltcG9ydGFudDtcbiAgfVxufVxuIiwiLy8gQ29tcG9uZW50IGZvY3VzIHNoYWRvd1xuQG1peGluIGNvbnRyb2wtc2hhZG93KCRjb2xvcjogJHByaW1hcnktY29sb3IpIHtcbiAgYm94LXNoYWRvdzogMCAwIDAgLjFyZW0gcmdiYSgkY29sb3IsIC4yKTtcbn1cblxuLy8gU2hhZG93IG1peGluXG5AbWl4aW4gc2hhZG93LXZhcmlhbnQoJG9mZnNldCkge1xuICBib3gtc2hhZG93OiAwICRvZmZzZXQgKCRvZmZzZXQgKyAuMDVyZW0pICogMiByZ2JhKCRkYXJrLWNvbG9yLCAuMyk7XG59XG4iLCIvLyBUZXh0IEVsbGlwc2lzXG5AbWl4aW4gdGV4dC1lbGxpcHNpcygpIHtcbiAgb3ZlcmZsb3c6IGhpZGRlbjtcbiAgdGV4dC1vdmVyZmxvdzogZWxsaXBzaXM7XG4gIHdoaXRlLXNwYWNlOiBub3dyYXA7XG59XG4iLCIvLyBUb2FzdCB2YXJpYW50IG1peGluXG5AbWl4aW4gdG9hc3QtdmFyaWFudCgkY29sb3I6ICRkYXJrLWNvbG9yKSB7XG4gIGJhY2tncm91bmQ6IHJnYmEoJGNvbG9yLCAuOTUpO1xuICBib3JkZXItY29sb3I6ICRjb2xvcjtcbn1cbiIsIiR0aXRsZS1mb250LWZhbWlseTogJGJhc2UtZm9udC1mYW1pbHksICRmYWxsYmFjay1mb250LWZhbWlseSAhZGVmYXVsdDsiLCJAbWl4aW4gYnJlYWtwb2ludCgkcG9pbnQpIHtcbiAgQGlmICRwb2ludCA9PSAyeCB7XG4gICAgQG1lZGlhIChtaW4td2lkdGg6JHNpemUtMngpIHtcbiAgICAgIEBjb250ZW50O1xuICAgIH1cbiAgfSBAZWxzZSBpZiAkcG9pbnQgPT0geGwge1xuICAgIEBtZWRpYSAobWF4LXdpZHRoOiAkc2l6ZS14bCkge1xuICAgICAgQGNvbnRlbnQ7XG4gICAgfVxuXG4gIH0gQGVsc2UgaWYgJHBvaW50ID09IGxnIHtcbiAgICBAbWVkaWEgKG1heC13aWR0aDogJHNpemUtbGcpIHtcbiAgICAgIEBjb250ZW50O1xuICAgIH1cbiAgfSBAZWxzZSBpZiAkcG9pbnQgPT0gbWQge1xuICAgIEBtZWRpYSAobWF4LXdpZHRoOiAkc2l6ZS1tZCkge1xuICAgICAgQGNvbnRlbnQ7XG4gICAgfVxuICB9IEBlbHNlIGlmICRwb2ludCA9PSBzbSB7XG4gICAgQG1lZGlhIChtYXgtd2lkdGg6ICRzaXplLXNtKSB7XG4gICAgICBAY29udGVudDtcbiAgICB9XG4gIH0gQGVsc2UgaWYgJHBvaW50ID09IHhzIHtcbiAgICBAbWVkaWEgKG1heC13aWR0aDogJHNpemUteHMpIHtcbiAgICAgIEBjb250ZW50O1xuICAgIH1cbiAgfSAgQGVsc2Uge1xuICAgIEB3YXJuIFwiQnJlYWtwb2ludCBtaXhpbiBzdXBwb3J0czogeHMsIHNtLCBtZCwgbGcsIHhsLCAyeFwiO1xuICB9XG59XG5cbkBtaXhpbiB2ZXJ0aWNhbC1hbGlnbigkcG9zaXRpb246IHJlbGF0aXZlKSB7XG4gIHBvc2l0aW9uOiAkcG9zaXRpb247XG4gIHRvcDogNTAlO1xuICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVkoLTUwJSk7XG59XG5cbkBtaXhpbiBidXR0b24tcHJpbWFyeSgpIHtcbiAgYmFja2dyb3VuZDogJHByaW1hcnktY29sb3I7XG4gIGJvcmRlci1jb2xvcjogJHByaW1hcnktY29sb3ItZGFyaztcbiAgY29sb3I6ICRsaWdodC1jb2xvcjtcbiAgJjpmb2N1cyxcbiAgJjpob3ZlciB7XG4gICAgYmFja2dyb3VuZDogZGFya2VuKCRwcmltYXJ5LWNvbG9yLWRhcmssIDIlKTtcbiAgICBib3JkZXItY29sb3I6IGRhcmtlbigkcHJpbWFyeS1jb2xvci1kYXJrLCA1JSk7XG4gICAgY29sb3I6ICRsaWdodC1jb2xvcjtcbiAgfVxuICAmOmFjdGl2ZSxcbiAgJi5hY3RpdmUge1xuICAgIGJhY2tncm91bmQ6IGRhcmtlbigkcHJpbWFyeS1jb2xvci1kYXJrLCA0JSk7XG4gICAgYm9yZGVyLWNvbG9yOiBkYXJrZW4oJHByaW1hcnktY29sb3ItZGFyaywgNyUpO1xuICAgIGNvbG9yOiAkbGlnaHQtY29sb3I7XG4gIH1cbn1cblxuQGZ1bmN0aW9uIHN0cmlwLXVuaXQoJHZhbHVlKSB7XG4gIEByZXR1cm4gJHZhbHVlIC8gKCR2YWx1ZSAqIDAgKyAxKTtcbn1cblxuQG1peGluIGZsdWlkLXR5cGUoJG1pbi12dywgJG1heC12dywgJG1pbi1mb250LXNpemUsICRtYXgtZm9udC1zaXplKSB7XG4gICR1MTogdW5pdCgkbWluLXZ3KTtcbiAgJHUyOiB1bml0KCRtYXgtdncpO1xuICAkdTM6IHVuaXQoJG1pbi1mb250LXNpemUpO1xuICAkdTQ6IHVuaXQoJG1heC1mb250LXNpemUpO1xuXG4gIEBpZiAkdTEgPT0gJHUyIGFuZCAkdTEgPT0gJHUzIGFuZCAkdTEgPT0gJHU0IHtcbiAgICAmIHtcbiAgICAgIGZvbnQtc2l6ZTogJG1pbi1mb250LXNpemU7XG4gICAgICBAbWVkaWEgc2NyZWVuIGFuZCAobWluLXdpZHRoOiAkbWluLXZ3KSB7XG4gICAgICAgIGZvbnQtc2l6ZTogY2FsYygjeyRtaW4tZm9udC1zaXplfSArICN7c3RyaXAtdW5pdCgkbWF4LWZvbnQtc2l6ZSAtICRtaW4tZm9udC1zaXplKX0gKiAoKDEwMHZ3IC0gI3skbWluLXZ3fSkgLyAje3N0cmlwLXVuaXQoJG1heC12dyAtICRtaW4tdncpfSkpO1xuICAgICAgfVxuICAgICAgQG1lZGlhIHNjcmVlbiBhbmQgKG1pbi13aWR0aDogJG1heC12dykge1xuICAgICAgICBmb250LXNpemU6ICRtYXgtZm9udC1zaXplO1xuICAgICAgfVxuICAgIH1cbiAgfVxufVxuIiwiaHRtbCB7XG4gIGhlaWdodDogMTAwJTtcbn1cblxuI2JvZHktd3JhcHBlciB7XG4gIC5jb250YWluZXIge1xuICAgIHBhZGRpbmc6ICR2ZXJ0LXBhZGRpbmc7XG4gIH1cblxuICAvLyBGaXhlZCBIZWFkZXIgc29sdXRpb25cbiAgLmhlYWRlci1maXhlZCAmIHtcbiAgICBwYWRkaW5nLXRvcDogJGhlYWRlci1oZWlnaHQtbGFyZ2U7XG4gIH1cbn1cblxuLmhlYWRlci1maXhlZCB7XG4gIC5oZXJvICsgI3N0YXJ0ID4gI2JvZHktd3JhcHBlciB7XG4gICAgcGFkZGluZy10b3A6IDA7XG4gIH1cbn1cblxuc2VjdGlvbi5zZWN0aW9uIHtcbiAgcGFkZGluZy1sZWZ0OiAkaG9yaXotcGFkZGluZztcbiAgcGFkZGluZy1yaWdodDogJGhvcml6LXBhZGRpbmc7XG4gIHBvc2l0aW9uOiByZWxhdGl2ZTtcbn1cblxuLm92ZXJsYXktbGlnaHQsIC5vdmVybGF5LWRhcmssIC5vdmVybGF5LWxpZ2h0LWdyYWRpZW50LCAub3ZlcmxheS1kYXJrLWdyYWRpZW50IHtcbiAgei1pbmRleDogMDtcbn1cblxuLy8gSGVyb1xuLmhlcm8ge1xuICBkaXNwbGF5OiBmbGV4O1xuICBhbGlnbi1pdGVtczogY2VudGVyO1xuICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjtcblxuICBwYWRkaW5nLXRvcDogNnJlbTtcbiAgcGFkZGluZy1ib3R0b206IDdyZW07XG4gIGJhY2tncm91bmQtc2l6ZTogY292ZXI7XG4gIGJhY2tncm91bmQtcG9zaXRpb246IGNlbnRlcjtcblxuICBoMSB7XG4gICAgY29sb3I6ICRoZWFkZXItdGV4dC1kYXJrO1xuICAgIGZvbnQtc2l6ZTogNHJlbTtcbiAgfVxuXG4gIGgyIHtcbiAgICBjb2xvcjogcmdiYSgkaGVhZGVyLXRleHQtZGFyaywgMC44KTtcbiAgICBmb250LXNpemU6IDIuNXJlbTtcbiAgfVxuXG4gICYuaGVyby1mdWxsc2NyZWVuIHtcbiAgICBtaW4taGVpZ2h0OiAxMDB2aDtcbiAgfVxuXG4gICYuaGVyby1sYXJnZSB7XG4gICAgbWluLWhlaWdodDogNTAwcHg7XG4gIH1cblxuICAmLmhlcm8tbWVkaXVtIHtcbiAgICBtaW4taGVpZ2h0OiA0MDBweDtcbiAgfVxuXG4gICYuaGVyby1zbWFsbCB7XG4gICAgbWluLWhlaWdodDogMTEwcHg7XG4gIH1cblxuICAmLmhlcm8tdGlueSB7XG4gICAgbWluLWhlaWdodDogOHJlbTtcbiAgfVxuICBcbiAgLmhlYWRlci1maXhlZCAmIHtcbiAgICBiYWNrZ3JvdW5kLXBvc2l0aW9uOiA1MCUgMDtcbiAgfVxuXG4gIC8vJi5wYXJhbGxheCB7XG4gIC8vICBiYWNrZ3JvdW5kLWF0dGFjaG1lbnQ6IGZpeGVkO1xuICAvL31cblxuICBAaW5jbHVkZSBicmVha3BvaW50KG1kKSB7XG4gICAgaDEge1xuICAgICAgZm9udC1zaXplOiAzcmVtO1xuICAgIH1cbiAgICBoMiB7XG4gICAgICBmb250LXNpemU6IDEuNzVyZW07XG4gICAgfVxuICB9XG5cbiAgQGluY2x1ZGUgYnJlYWtwb2ludChzbSkge1xuICAgIGgxIHtcbiAgICAgIGZvbnQtc2l6ZTogMnJlbTtcbiAgICB9XG4gICAgaDIge1xuICAgICAgZm9udC1zaXplOiAxLjI1cmVtO1xuICAgIH1cbiAgfVxuXG5cbiAgJi50ZXh0LWxpZ2h0IHtcbiAgICBoMSB7XG4gICAgICBjb2xvcjogJGhlYWRlci10ZXh0LWxpZ2h0O1xuICAgIH1cbiAgICBoMiB7XG4gICAgICBjb2xvcjogcmdiYSgkaGVhZGVyLXRleHQtbGlnaHQsIDAuOCk7XG4gICAgfVxuICB9XG5cbiAgcCB7XG4gICAgZm9udC1zaXplOiAuOXJlbTtcbiAgICBmb250LXdlaWdodDogMzAwO1xuICB9XG5cbiAgI3RvLXN0YXJ0IHtcbiAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XG4gICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgIGJvdHRvbTogMTBweDtcbiAgICBmb250LXNpemU6IDJyZW07XG4gICAgY3Vyc29yOiBwb2ludGVyO1xuICB9XG59XG5cbi8vIE92ZXJsYXlcbi5pbWFnZS1vdmVybGF5IHtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICB0b3A6IDA7XG4gIGJvdHRvbTogMDtcbiAgbGVmdDogMDtcbiAgcmlnaHQ6IDA7XG4gIHotaW5kZXg6IC0xO1xuXG4gIC5vdmVybGF5LWxpZ2h0ICYge1xuICAgIGJhY2tncm91bmQ6IHJnYmEoI2ZmZiwgMC40KTtcbiAgfVxuXG4gIC5vdmVybGF5LWxpZ2h0LWdyYWRpZW50ICYge1xuICAgIGJhY2tncm91bmQ6IGxpbmVhci1ncmFkaWVudCh0byBib3R0b20sIHJnYmEoI2ZmZiwwLjUpLCByZ2JhKCNmZmYsMC4yKSk7XG4gIH1cblxuICAub3ZlcmxheS1kYXJrICYge1xuICAgIGJhY2tncm91bmQ6IHJnYmEoIzAwMCwgMC40KTtcbiAgfVxuXG4gIC5vdmVybGF5LWRhcmstZ3JhZGllbnQgJiB7XG4gICAgYmFja2dyb3VuZDogbGluZWFyLWdyYWRpZW50KHRvIGJvdHRvbSwgcmdiYSgjMDAwLDAuNSksIHJnYmEoIzAwMCwwLjIpKTtcbiAgfVxufVxuXG5cblxuXG5cblxuXG5cblxuIiwiaHRtbCB7XG4gIEBpbmNsdWRlIGZsdWlkLXR5cGUoJHNpemUteHMsICRzaXplLXhsLCAkbWluLXJlc3BvbnNpdmUtZm9udC1zaXplLCAkaHRtbC1mb250LXNpemUpO1xufVxuXG4vLyBIZWFkZXIgT3ZlcnJpZGVzXG5oMSwgaDIsIGgzLCBoNCwgaDUsIGg2IHtcbiAgbWFyZ2luLXRvcDogMnJlbTtcbiAgZm9udC1mYW1pbHk6ICR0aXRsZS1mb250LWZhbWlseTtcbiAgY29sb3I6IGRhcmtlbigkYm9keS1mb250LWNvbG9yLCAxMCUpO1xufVxuXG5oMSwgLmgxIHtcbiAgZm9udC1zaXplOiAzcmVtO1xufVxuXG5oMiwgLmgyIHtcbiAgZm9udC1zaXplOiAxLjhyZW07XG59XG5cbmg2LCAuaDYge1xuICBmb250LXdlaWdodDogNDAwO1xufVxuXG4udGl0bGUtY2VudGVyIHtcbiAgaDEsIGgyIHtcbiAgICB0ZXh0LWFsaWduOiBjZW50ZXI7XG4gIH1cbn1cblxuLnRpdGxlLWgxaDIge1xuICBoMSAge1xuICAgIGZvbnQtd2VpZ2h0OiAxMDA7XG4gICAgbWFyZ2luLWJvdHRvbTogMDtcbiAgICBsaW5lLWhlaWdodDogMS4xO1xuXG4gICAgc3Ryb25nLCBib2xkIHtcbiAgICAgIGZvbnQtd2VpZ2h0OiA0MDA7XG4gICAgfVxuICB9XG4gIGgxICsgaDIge1xuICAgIGxpbmUtaGVpZ2h0OiAxLjE7XG4gICAgbWFyZ2luLXRvcDogMDtcbiAgfVxuXG59XG5cbi8vIFR5cG9ncmFwaHkgSGludHNcbi50aXRsZS1oMWgyLCAudGl0bGUtY2VudGVyIHtcbiAgaDEgKyBoMiB7XG4gICAgbWFyZ2luLWJvdHRvbTogNTBweDtcbiAgICBmb250LXdlaWdodDogNzAwO1xuICB9XG59XG5cbmE6Zm9jdXMge1xuICBvdXRsaW5lOiBub25lICFpbXBvcnRhbnQ7XG4gIGJveC1zaGFkb3c6IG5vbmUgIWltcG9ydGFudDtcbn1cblxuaW1nIHtcbiAgbWF4LXdpZHRoOiAxMDAlO1xufVxuXG4vLyBUYWJsZXNcbi50YWJsZSB0YWJsZSB7XG4gIGJvcmRlci1zcGFjaW5nOiAwO1xuICBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlO1xuICB3aWR0aDogMTAwJTtcbn1cblxuXG4vLyBDb2RlYmxvY2tzXG5wcmUgY29kZSwgcHJlLnhkZWJ1Zy12YXItZHVtcHtcbiAgYmFja2dyb3VuZDogI2ZhZmFmYTtcbiAgZGlzcGxheTogYmxvY2s7XG4gIHBhZGRpbmc6IDFyZW0gIWltcG9ydGFudDtcbiAgbGluZS1oZWlnaHQ6IDEuNTtcbiAgY29sb3I6IGluaGVyaXQ7XG4gIGJvcmRlci1yYWRpdXM6IDJweDtcbiAgb3ZlcmZsb3cteDogYXV0bztcbn1cblxucHJlW2NsYXNzKj1cImxhbmd1YWdlLVwiXSB7XG4gIGNvZGUge1xuICAgIGJvcmRlci1yYWRpdXM6IGluaGVyaXQ7XG4gICAgcGFkZGluZzogMCAhaW1wb3J0YW50O1xuICAgIG92ZXJmbG93LXg6IGluaXRpYWw7XG4gIH1cbn1cblxucHJlIHtcbiAgY29kZTpub3QoLmhsanMpOm5vdChbY2xhc3MqPVwibGFuZ3VhZ2UtXCJdKSB7XG4gICAgYmFja2dyb3VuZDogI2Y4ZjhmODtcbiAgfVxufVxuXG4vLyBJY29uIFR3ZWFrc1xuaS5mYSB7XG5cblxuICAmLmZhLWhlYXJ0LCAmLmZhLWhlYXJ0LW8ge1xuICAgICYucHVsc2Uge1xuICAgICAgY29sb3I6ICM5MjBcbiAgICB9XG4gIH1cbn1cblxuLy8gRm9udCBXZWlnaHRzXG5iLFxuc3Ryb25nIHtcbiAgZm9udC13ZWlnaHQ6IDcwMDtcbn1cblxuLmhlYXZ5IHtcbiAgZm9udC13ZWlnaHQ6IDcwMDtcbn1cblxuLmxpZ2h0IHtcbiAgZm9udC13ZWlnaHQ6IDIwMDtcbn1cblxuLy8gQ29sb3JzXG4udGV4dC1saWdodCB7XG4gIGNvbG9yOiByZ2JhKCRsaWdodC1jb2xvciwgMC44KTtcblxuICBoMSwgaDIsIGgzLCBoNCwgaDUsIGg2IHtcbiAgICBjb2xvcjogcmdiYSgkbGlnaHQtY29sb3IsIDAuOSk7XG4gIH1cbn1cblxuLy8gRXJyb3IgY29uZmlndXJhdGlvblxuI2Vycm9yIHtcbiAgdGV4dC1hbGlnbjogY2VudGVyO1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gIG1hcmdpbi10b3A6IDVyZW07XG5cbiAgLmljb24ge1xuICAgIGZvbnQtc2l6ZTogNTBweDtcbiAgfVxufVxuXG4vLyBNZXNzYWdlc1xuI21lc3NhZ2VzIHtcbiAgbWFyZ2luLWJvdHRvbTogMXJlbTtcblxuICAuaWNvbiB7XG4gICAgZm9udC1zaXplOiAxcmVtO1xuICB9XG59XG5cbi8vIExpc3RzXG51bCxcbm9sIHtcbiAgbWFyZ2luLWxlZnQ6ICR1bml0LTg7XG5cbiAgdWwsXG4gIG9sIHtcbiAgICBtYXJnaW4tbGVmdDogJHVuaXQtODtcbiAgfVxufVxuXG51bCB7XG4gIGxpc3Qtc3R5bGU6IGRpc2Mgb3V0c2lkZTtcbn1cblxub2wge1xuICBsaXN0LXN0eWxlOiBkZWNpbWFsIG91dHNpZGU7XG59XG5cbi8vIE5vdGljZXNcbi5ub3RpY2VzIHtcbiAgbWFyZ2luOiAxLjVyZW0gMDtcbiAgcCB7XG4gICAgbWFyZ2luOiAxcmVtIDA7XG4gIH1cbn1cblxuXG4iLCJmb3JtIHtcbiAgLmJ1dHRvbi13cmFwcGVyIHtcbiAgICBtYXJnaW4tdG9wOiAwLjc1cmVtO1xuICAgIG1hcmdpbi1ib3R0b206IDFyZW07XG4gIH1cblxuICBzcGFuLnJlcXVpcmVkIHtcbiAgICBjb2xvcjogJGVycm9yLWNvbG9yO1xuICAgIGZvbnQtd2VpZ2h0OiA3MDA7XG4gICAgZm9udC1zaXplOiAxLjJyZW07XG4gIH1cblxuICAuZm9ybS1pbnB1dFt0eXBlPXJhbmdlXSB7XG4gICAgYXBwZWFyYW5jZTogc2xpZGVyLWhvcml6b250YWw7XG4gICAgJjpmb2N1cyB7XG4gICAgICBib3gtc2hhZG93OiBub25lO1xuICAgICAgYm9yZGVyOiBub25lO1xuICAgIH1cbiAgfVxuXG4gIC8qKiBSZXNldCBzb21lIGRlZmF1bHRzIGZvciBRdWFyayBUaGVtZSAqKi9cbiAgLmZvcm0tZ3JvdXA6bm90KC5mb3JtLWZpZWxkLXRvZ2dsZWFibGUpIHtcbiAgICAuY2hlY2tib3hlcyB7XG4gICAgICBkaXNwbGF5OiBpbmhlcml0O1xuXG4gICAgICBsYWJlbCB7XG4gICAgICAgIGRpc3BsYXk6IGluaGVyaXQ7XG4gICAgICAgIHBhZGRpbmc6ICgoJGNvbnRyb2wtc2l6ZS1zbSAtICRsaW5lLWhlaWdodCkgLyAyKSAkY29udHJvbC1wYWRkaW5nLXggKCgkY29udHJvbC1zaXplLXNtIC0gJGxpbmUtaGVpZ2h0KSAvIDIpICgkY29udHJvbC1pY29uLXNpemUgKyAkY29udHJvbC1wYWRkaW5nLXgpO1xuICAgICAgICBtYXJnaW46IGluaGVyaXQ7XG5cbiAgICAgICAgJjpiZWZvcmUge1xuICAgICAgICAgIGRpc3BsYXk6IG5vbmU7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH1cblxufVxuXG4jZ3Jhdi1sb2dpbiB7XG5cbiAgPiBmb3JtIHtcbiAgICBtYXJnaW46IDJyZW0gYXV0byAwO1xuICAgIG1heC13aWR0aDogMzUwcHg7XG4gIH1cbiAgLmZvcm0tbGFiZWwge1xuICAgIGRpc3BsYXk6IG5vbmU7XG4gIH1cbiAgLmZvcm0tZGF0YSB7XG4gICAgbWFyZ2luOiAxcmVtIDA7XG4gIH1cbiAgLmZvcm0taW5wdXQge1xuICAgIHRleHQtYWxpZ246IGNlbnRlcjtcbiAgfVxuICAuYnV0dG9uLXdyYXBwZXIge1xuICAgIHRleHQtYWxpZ246IHJpZ2h0O1xuXG4gICAgLmZvcm0tZGF0YS5yZW1lbWJlcm1lIHtcbiAgICAgIG1hcmdpbjogMDtcbiAgICAgIGZsb2F0OiBsZWZ0O1xuICAgIH1cbiAgfVxuXG4gIC5sb2dpbi1mb3JtIHtcbiAgICBidXR0b25bdHlwZT1cInN1Ym1pdFwiXSB7XG4gICAgICBAaW5jbHVkZSBidXR0b24tcHJpbWFyeTtcbiAgICB9XG4gIH1cblxuICAudHdvZmEtZm9ybSB7XG4gICAgYnV0dG9uW3R5cGU9XCJzdWJtaXRcIl06Zmlyc3QtY2hpbGQge1xuICAgICAgQGluY2x1ZGUgYnV0dG9uLXByaW1hcnk7XG4gICAgICBmbG9hdDogcmlnaHQ7XG4gICAgICBtYXJnaW4tbGVmdDogNHB4O1xuICAgIH1cbiAgfVxufVxuIiwiLm1vYmlsZS1jb250YWluZXIge1xuICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gIC8vd2lkdGg6IDEwMCU7XG4gIC8vaGVpZ2h0OiAxMDAlO1xuICB0b3A6IDQwJTtcbiAgbGVmdDogMDtcbiAgbWFyZ2luOiAgMCBhdXRvO1xuICB6LWluZGV4OiAyO1xufVxuXG4ubW9iaWxlLWxvZ28ge1xuICBzdmcsIGltZyB7XG4gICAgaGVpZ2h0OiA0MnB4O1xuICAgIG1hcmdpbi10b3A6IC43cmVtO1xuICAgIG1hcmdpbi1sZWZ0OiAxLjRyZW07XG5cbiAgICBwYXRoIHtcbiAgICAgIGZpbGw6ICRsaWdodC1jb2xvcjtcbiAgICB9XG5cbiAgfVxufVxuXG4vLyBIYW1idXJnZXIgTWVudVxuLm1vYmlsZS1tZW51IHtcblxuICBkaXNwbGF5OiBub25lO1xuICB0b3A6IDA7XG4gIHJpZ2h0OiAwO1xuICB6LWluZGV4OiAzO1xuXG4gIC5oZWFkZXItZml4ZWQgJiB7XG4gICAgcG9zaXRpb246IGZpeGVkO1xuICB9XG5cbiAgQGluY2x1ZGUgYnJlYWtwb2ludChtZCkge1xuICAgIGRpc3BsYXk6IGJsb2NrO1xuICB9XG5cbiAgLmJ1dHRvbl9jb250YWluZXIge1xuICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICB0b3A6IDEuM3JlbTtcbiAgICByaWdodDogJGhvcml6LXBhZGRpbmc7XG4gICAgaGVpZ2h0OiAkbW9iaWxlLWJ1dHRvbi1oZWlnaHQ7XG4gICAgd2lkdGg6ICRtb2JpbGUtYnV0dG9uLXdpZHRoO1xuICAgIGN1cnNvcjogcG9pbnRlcjtcbiAgICB6LWluZGV4OiAxMDA7XG4gICAgdHJhbnNpdGlvbjogb3BhY2l0eSAuMjVzIGVhc2UsIHRvcCAwLjVzIGVhc2U7XG5cbiAgICAkYmFyLW9mZnNldDogJG1vYmlsZS1idXR0b24taGVpZ2h0IC8gMztcblxuICAgICY6aG92ZXIge1xuICAgICAgb3BhY2l0eTogLjc7XG4gICAgfVxuXG4gICAgJi5hY3RpdmUge1xuICAgICAgcG9zaXRpb246IGZpeGVkO1xuXG4gICAgICAudG9wIHtcbiAgICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKCRiYXItb2Zmc2V0KSB0cmFuc2xhdGVYKDApIHJvdGF0ZSg0NWRlZyk7XG4gICAgICAgIGJhY2tncm91bmQ6ICRtb2JpbGUtY29sb3ItYWN0aXZlO1xuICAgICAgfVxuICAgICAgLm1pZGRsZSB7XG4gICAgICAgIG9wYWNpdHk6IDA7XG4gICAgICAgIGJhY2tncm91bmQ6ICRtb2JpbGUtY29sb3ItYWN0aXZlO1xuICAgICAgfVxuXG4gICAgICAuYm90dG9tIHtcbiAgICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKC0oJGJhci1vZmZzZXQpKSB0cmFuc2xhdGVYKDApIHJvdGF0ZSgtNDVkZWcpO1xuICAgICAgICBiYWNrZ3JvdW5kOiAkbW9iaWxlLWNvbG9yLWFjdGl2ZTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICBzcGFuIHtcbiAgICAgIGJhY2tncm91bmQ6ICRtb2JpbGUtY29sb3ItbWFpbjtcbiAgICAgIGJvcmRlcjogbm9uZTtcbiAgICAgIGhlaWdodDogNHB4O1xuICAgICAgd2lkdGg6IDEwMCU7XG4gICAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgICB0b3A6IDA7XG4gICAgICBsZWZ0OiAwO1xuICAgICAgdHJhbnNpdGlvbjogIGFsbCAuMzVzIGVhc2U7XG4gICAgICBjdXJzb3I6IHBvaW50ZXI7XG5cbiAgICAgICY6bnRoLW9mLXR5cGUoMikge1xuICAgICAgICB0b3A6ICRiYXItb2Zmc2V0O1xuICAgICAgfVxuXG4gICAgICAmOm50aC1vZi10eXBlKDMpIHtcbiAgICAgICAgdG9wOiAkYmFyLW9mZnNldCAqIDI7XG4gICAgICB9XG4gICAgfVxuICB9XG59XG5cbi5vdmVybGF5IHtcbiAgcG9zaXRpb246IGZpeGVkO1xuICBiYWNrZ3JvdW5kOiAjMDAwO1xuICB0b3A6IDA7XG4gIGxlZnQ6IDA7XG4gIHdpZHRoOiAxMDAlO1xuICBoZWlnaHQ6IDAlO1xuICBvcGFjaXR5OiAwO1xuICB2aXNpYmlsaXR5OiBoaWRkZW47XG4gIHRyYW5zaXRpb246IG9wYWNpdHkgLjM1cywgdmlzaWJpbGl0eSAuMzVzLCBoZWlnaHQgLjM1cztcblxuICAmLm9wZW4ge1xuICAgIG9wYWNpdHk6IC45NTtcbiAgICB2aXNpYmlsaXR5OiB2aXNpYmxlO1xuICAgIGhlaWdodDogMTAwJTtcblxuICB9XG4gIG5hdiB7XG5cbiAgICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gICAgbWFyZ2luOiAwIGF1dG87XG4gICAgdGV4dC1hbGlnbjogY2VudGVyO1xuICB9XG59XG5cbi5vdmVybGF5LW1lbnUge1xuICBoZWlnaHQ6IGNhbGMoMTAwJSAtIDkwcHgpO1xuICBvdmVyZmxvdy15OiBzY3JvbGw7XG5cbiAgJiA+IC50cmVlIHtcbiAgICB0ZXh0LWFsaWduOiBsZWZ0O1xuICB9XG59XG5cblxuLnRyZWVtZW51IHtcblxuICAmLnRyZWVtZW51LXJvb3Qge1xuICAgIG1hcmdpbjogMXJlbTtcbiAgfVxuXG4gIGxpIHtcbiAgICBsaXN0LXN0eWxlOiBub25lO1xuICAgIG1hcmdpbjogMCAwIDFweDtcbiAgICBwYWRkaW5nOiA1cHggMDtcbiAgICBsaW5lLWhlaWdodDogMS4ycmVtO1xuXG4gICAgYmFja2dyb3VuZDogcmdiYSgkZ3JheS1jb2xvci1kYXJrLDAuMSk7XG5cbiAgICBhIHtcbiAgICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgICAgbWFyZ2luLWxlZnQ6IDEuMnJlbTtcbiAgICAgIGZvbnQtc2l6ZTogMXJlbTtcblxuICAgICAgJjpob3ZlciwgJjpmb2N1cywgJi5hY3RpdmUge1xuICAgICAgICBjb2xvcjogJHByaW1hcnktY29sb3ItbGlnaHQgIWltcG9ydGFudDtcbiAgICAgICAgdGV4dC1kZWNvcmF0aW9uOiBub25lO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIHVsIHtcbiAgICBtYXJnaW46IDAgMCAwIDFyZW07XG4gIH1cblxuICAudG9nZ2xlciB7XG4gICAgY3Vyc29yOiBwb2ludGVyO1xuICAgIHZlcnRpY2FsLWFsaWduOiB0b3A7XG4gICAgZm9udC1zaXplOiAxLjFyZW07XG4gICAgbGluZS1oZWlnaHQ6IDFyZW07XG4gICAgcGFkZGluZy1sZWZ0OiA1cHg7XG4gICAgZmxvYXQ6IGxlZnQ7XG5cbiAgICAmOmJlZm9yZSB7XG4gICAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7IG1hcmdpbi1yaWdodDogMnB0O1xuICAgIH1cbiAgfVxuXG4gIGxpLnRyZWUtZW1wdHkgID4gLnRvZ2dsZXIge1xuICAgIG9wYWNpdHk6IDAuMzsgY3Vyc29yOiBkZWZhdWx0O1xuXG4gICAgJjpiZWZvcmUge1xuICAgICAgY29udGVudDogXCJcXDIwMjJcIjtcbiAgICB9XG4gIH1cblxuICBsaS50cmVlLWNsb3NlZCA+IC50b2dnbGVyOmJlZm9yZSB7XG4gICAgY29udGVudDogXCIrXCI7XG4gIH1cblxuICBsaS50cmVlLW9wZW5lZCA+IC50b2dnbGVyOmJlZm9yZSB7XG4gICAgY29udGVudDogXCJcXDIyMTJcIjtcbiAgfVxufVxuXG4ubW9iaWxlLW5hdi1vcGVuIHtcbiAgb3ZlcmZsb3cteTogaGlkZGVuO1xufVxuIiwiLmRlZmF1bHQtYW5pbWF0aW9uIHtcbiAgdHJhbnNpdGlvbjogYWxsIDAuNXMgZWFzZTtcbn1cblxuLy8gUHVsc2UgQW5pbWF0aW9uXG4ucHVsc2Uge1xuICBhbmltYXRpb24tbmFtZTogcHVsc2VfYW5pbWF0aW9uO1xuICBhbmltYXRpb24tZHVyYXRpb246IDIwMDBtcztcbiAgdHJhbnNmb3JtLW9yaWdpbjo3MCUgNzAlO1xuICBhbmltYXRpb24taXRlcmF0aW9uLWNvdW50OiBpbmZpbml0ZTtcbiAgYW5pbWF0aW9uLXRpbWluZy1mdW5jdGlvbjogbGluZWFyO1xufVxuXG5Aa2V5ZnJhbWVzIHB1bHNlX2FuaW1hdGlvbiB7XG4gIDAlIHsgdHJhbnNmb3JtOiBzY2FsZSgxKTsgfVxuICAzMCUgeyB0cmFuc2Zvcm06IHNjYWxlKDEpOyB9XG4gIDQwJSB7IHRyYW5zZm9ybTogc2NhbGUoMS4wOCk7IH1cbiAgNTAlIHsgdHJhbnNmb3JtOiBzY2FsZSgxKTsgfVxuICA2MCUgeyB0cmFuc2Zvcm06IHNjYWxlKDEpOyB9XG4gIDcwJSB7IHRyYW5zZm9ybTogc2NhbGUoMS4wNSk7IH1cbiAgODAlIHsgdHJhbnNmb3JtOiBzY2FsZSgxKTsgfVxuICAxMDAlIHsgdHJhbnNmb3JtOiBzY2FsZSgxKTsgfVxufVxuIiwiI2hlYWRlciB7XG4gIHdpZHRoOiAxMDAlO1xuICBoZWlnaHQ6ICRoZWFkZXItaGVpZ2h0LWxhcmdlO1xuICBib3JkZXItYm90dG9tOiAxcHggc29saWQgcmdiYSgkZ3JheS1jb2xvciwgMC4yKTtcbiAgQGV4dGVuZCAuZGVmYXVsdC1hbmltYXRpb247XG5cbiAgZm9udC1zaXplOiAwLjdyZW07XG4gIGZvbnQtd2VpZ2h0OiA3MDA7XG5cbiAgYmFja2dyb3VuZDogJGxpZ2h0LWNvbG9yO1xuICBjb2xvcjogJGRhcmstY29sb3I7XG5cbiAgYSB7XG4gICAgY29sb3I6ICRkYXJrLWNvbG9yO1xuICB9XG5cbiAgLmxvZ28gc3ZnIHBhdGgge1xuICAgIGZpbGw6ICRoZWFkZXItY29sb3ItZGFyaztcbiAgfVxuXG4gIC5oZWFkZXItZGFyayAmOm5vdCguc2Nyb2xsZWQpIHtcbiAgICBiYWNrZ3JvdW5kOiAkaGVhZGVyLWNvbG9yLWRhcms7XG4gICAgY29sb3I6ICRsaWdodC1jb2xvcjtcbiAgICBhIHtcbiAgICAgIGNvbG9yOiByZ2JhKCRsaWdodC1jb2xvciwgMC43KSAhaW1wb3J0YW50O1xuICAgIH1cbiAgICBhLmFjdGl2ZSB7XG4gICAgICBjb2xvcjogJGxpZ2h0LWNvbG9yICFpbXBvcnRhbnQ7XG4gICAgfVxuICAgIC5kcm9wbWVudSB1bCB1bCBhIHtcbiAgICAgICAgY29sb3I6ICRkYXJrLWNvbG9yICFpbXBvcnRhbnQ7XG4gICAgfVxuICAgIC5sb2dvIHN2ZyBwYXRoIHtcbiAgICAgIGZpbGw6ICRsaWdodC1jb2xvcjtcbiAgICB9XG4gIH1cblxuICAuaGVhZGVyLWRhcmsuaGVhZGVyLXRyYW5zcGFyZW50ICY6bm90KC5zY3JvbGxlZCkge1xuICAgIGJhY2tncm91bmQ6IHJnYmEoIzAwMCwgMC4wNSk7XG4gIH1cblxuICAuaGVhZGVyLXRyYW5zcGFyZW50ICY6bm90KC5zY3JvbGxlZCkge1xuICAgIGJhY2tncm91bmQ6IHJnYmEoI2ZmZiwgMC4wNSk7XG4gICAgLy9ib3JkZXItYm90dG9tOiAwICFpbXBvcnRhbnQ7XG4gIH1cblxuICAubmF2YmFyLXNlY3Rpb24ge1xuICAgIGhlaWdodDogJGhlYWRlci1oZWlnaHQtbGFyZ2U7XG4gICAgQGV4dGVuZCAuZGVmYXVsdC1hbmltYXRpb247XG5cbiAgICBAaW5jbHVkZSBicmVha3BvaW50KG1kKSB7XG4gICAgICBtYXJnaW4tcmlnaHQ6IDJyZW07XG4gICAgfVxuICB9XG5cbiAgLm5hdmJhci1zZWN0aW9uLmRlc2t0b3AtbWVudSB7XG4gICAgQGluY2x1ZGUgYnJlYWtwb2ludChtZCkge1xuICAgICAgZGlzcGxheTogbm9uZTtcbiAgICB9XG4gIH1cblxuICAubG9nbyB7XG4gICAgc3ZnLCBpbWcge1xuICAgICAgaGVpZ2h0OiA0MnB4O1xuICAgICAgZGlzcGxheTogaW5oZXJpdDtcbiAgICAgIEBleHRlbmQgLmRlZmF1bHQtYW5pbWF0aW9uO1xuICAgIH1cbiAgfVxuXG4gIC8vIEZpeGVkIEhlYWRlciBzb2x1dGlvblxuICAuaGVhZGVyLWZpeGVkICYge1xuICAgIHBvc2l0aW9uOiBmaXhlZDtcbiAgICB0b3A6IDA7XG4gICAgei1pbmRleDogMjtcbiAgfVxufVxuXG4vLyBBbmltYXRlIEZpeGVkIEhlYWRlclxuYm9keS5oZWFkZXItZml4ZWQuaGVhZGVyLWFuaW1hdGVkIHtcbiAgI2hlYWRlci5zY3JvbGxlZCB7XG4gICAgaGVpZ2h0OiAkaGVhZGVyLWhlaWdodC1zbWFsbDtcblxuICAgIC5uYXZiYXItc2VjdGlvbiB7XG4gICAgICBoZWlnaHQ6ICRoZWFkZXItaGVpZ2h0LXNtYWxsO1xuICAgIH1cblxuICAgIC5sb2dvIHtcbiAgICAgIHN2ZywgaW1nIHtcbiAgICAgICAgaGVpZ2h0OiAyOHB4O1xuICAgICAgfVxuICAgIH1cblxuICAgIH4gLm1vYmlsZS1tZW51IC5idXR0b25fY29udGFpbmVyIHtcbiAgICAgIHRvcDogMC41cmVtO1xuICAgIH1cbiAgfVxufVxuXG4ubG9naW4tc3RhdHVzLXdyYXBwZXIge1xuICB3aGl0ZS1zcGFjZTogbm93cmFwO1xufVxuIiwiLy8gU3RpY2t5IEZvb3RlciBzb2x1dGlvblxuYm9keS5zdGlja3ktZm9vdGVyIHtcbiAgaGVpZ2h0OiAxMDAlO1xuICBtaW4taGVpZ2h0OiAxMDB2aDtcbiAgZGlzcGxheTogZmxleDtcbiAgZmxleC1kaXJlY3Rpb246IGNvbHVtbjtcblxuICAjcGFnZS13cmFwcGVyIHtcbiAgICBmbGV4OiAxIDAgYXV0bztcbiAgfVxufVxuXG4jZm9vdGVyIHtcbiAgY29sb3I6ICNhY2IzYzI7XG4gIHBhZGRpbmc6IDFyZW0gJGhvcml6LXBhZGRpbmcgMDtcbiAgdGV4dC1hbGlnbjogY2VudGVyO1xufSIsIi5kcm9wbWVudSB7XG5cbiAgQGluY2x1ZGUgYnJlYWtwb2ludChtZCkge1xuICAgIGRpc3BsYXk6IG5vbmU7XG4gIH1cblxuICB1bCB7XG4gICAgd2hpdGUtc3BhY2U6IG5vd3JhcDtcbiAgICBtYXJnaW46IDA7XG4gICAgZGlzcGxheTogZmxleDtcblxuICAgIGxpIHtcbiAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTtcbiAgICAgIG1hcmdpbjogMDtcblxuICAgICAgYSB7XG4gICAgICAgIHRleHQtZGVjb3JhdGlvbjogbm9uZTtcbiAgICAgICAgcGFkZGluZzogJGRyb3BtZW51LXZlcnQtcGFkZGluZyAoJGRyb3BtZW51LWhvcml6LXBhZGRpbmcgKyAkZHJvcG1lbnUtY2hpbGQtcGFkZGluZykgJGRyb3BtZW51LXZlcnQtcGFkZGluZyAkZHJvcG1lbnUtaG9yaXotcGFkZGluZztcbiAgICAgICAgZGlzcGxheTogYmxvY2s7XG5cbiAgICAgICAgJjpob3ZlciwgJjpmb2N1cywgJi5hY3RpdmUge1xuICAgICAgICAgIGNvbG9yOiAkZHJvcG1lbnUtaG92ZXItdGV4dCAhaW1wb3J0YW50O1xuICAgICAgICB9XG5cbiAgICAgICAgJjpiZWZvcmUge1xuICAgICAgICAgIGNvbnRlbnQ6ICdcXGYxMDcnO1xuICAgICAgICAgIGZvbnQtZmFtaWx5OiAnRm9udEF3ZXNvbWUnO1xuICAgICAgICAgIGRpc3BsYXk6IGlubGluZS1ibG9jaztcbiAgICAgICAgICB2ZXJ0aWNhbC1hbGlnbjogbWlkZGxlO1xuICAgICAgICAgIGZsb2F0OiByaWdodDtcbiAgICAgICAgICBtYXJnaW4tcmlnaHQ6IC0gKDIgKiRkcm9wbWVudS1jaGlsZC1wYWRkaW5nKTtcbiAgICAgICAgfVxuXG4gICAgICAgICY6b25seS1jaGlsZCB7XG4gICAgICAgICAgLy9iYWNrZ3JvdW5kOiBub25lO1xuICAgICAgICAgIHBhZGRpbmctcmlnaHQ6ICRkcm9wbWVudS1ob3Jpei1wYWRkaW5nO1xuXG4gICAgICAgICAgJjpiZWZvcmUge1xuICAgICAgICAgICAgY29udGVudDogJyc7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgICY6aG92ZXIge1xuICAgICAgICAmID4gdWwge1xuICAgICAgICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgICAgICAgIHZpc2liaWxpdHk6IHZpc2libGU7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICB1bCBsaSBhOmJlZm9yZSB7XG4gICAgICBjb250ZW50OiAnXFxmMTA1JztcbiAgICB9XG5cbiAgICB1bCB7XG4gICAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgICB0b3A6IDEwMCU7XG4gICAgICBsaXN0LXN0eWxlOiBub25lO1xuICAgICAgYmFja2dyb3VuZDogJGRyb3BtZW51LWJnO1xuICAgICAgYm94LXNoYWRvdzogJGRyb3BtZW51LXNoYWRvdztcbiAgICAgIHZpc2liaWxpdHk6IGhpZGRlbjtcblxuICAgICAgdWwge1xuICAgICAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgICAgIGxlZnQ6IDEwMCU7XG4gICAgICAgIHRvcDogMDtcbiAgICAgIH1cbiAgICB9XG5cbiAgfVxuXG4gICYgPiB1bCA+IGxpIHtcbiAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XG4gIH1cblxuICAvLyBBbmltYXRpb24gb3B0aW9uc1xuICAmLmFuaW1hdGVkIHtcbiAgICB1bCBsaSB7XG4gICAgICB0cmFuc2l0aW9uOiBiYWNrZ3JvdW5kIC43cywgY29sb3IgMC41cztcbiAgICB9XG5cbiAgICB1bCBsaTpob3ZlciA+IHVsIHtcbiAgICAgIG9wYWNpdHk6IDE7XG4gICAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVkoMCk7XG4gICAgfVxuXG4gICAgdWwgdWwge1xuICAgICAgdHJhbnNpdGlvbjogdHJhbnNmb3JtIC4zcywgb3BhY2l0eSAuNXM7XG4gICAgICBvcGFjaXR5OiAwO1xuICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKC0xMHB4KTtcbiAgICB9XG4gIH1cbn0iLCIvKiogRXh0cmEgY29sdW1ucyBzcGFjaW5nICoqL1xuLmV4dHJhLXNwYWNpbmc6bm90KC5jb2wtMTIpLCA6bm90KC5jb2wxMikgPiAuZS1jb250ZW50IHtcbiAgcGFkZGluZy1yaWdodDogMXJlbTtcblxuICBAaW5jbHVkZSBicmVha3BvaW50KG1kKSB7XG4gICAgcGFkZGluZy1yaWdodDogMDtcbiAgfVxufVxuXG4vKiogQnJlYWRjcnVtYnMgc3R5bGluZyAqKi9cbiNicmVhZGNydW1icyB7XG4gIHBhZGRpbmctbGVmdDogMDtcbiAgZGlzcGxheTogZmxleDtcbiAgYWxpZ24taXRlbXM6IGNlbnRlcjtcbiAgbWFyZ2luLXRvcDogLTFyZW07XG4gIG1hcmdpbi1ib3R0b206IDFyZW07XG5cblxuICBpIHtcbiAgICBkaXNwbGF5OiBub25lO1xuICB9XG5cbiAgc3BhbiwgYSB7XG4gICAgcGFkZGluZzogMCAwLjVyZW07XG4gICAgJjpmaXJzdC1jaGlsZCB7XG4gICAgICBwYWRkaW5nLWxlZnQ6IDA7XG4gICAgfVxuICB9XG5cbiAgc3BhbiwgYSB7XG4gICAgJjpub3QoOmZpcnN0LWNoaWxkKTo6YmVmb3JlIHtcbiAgICAgIGNvbG9yOiAjZTdlOWVkO1xuICAgICAgY29udGVudDogXCIvXCI7XG4gICAgICBwYWRkaW5nLXJpZ2h0OiAxcmVtO1xuICAgIH1cbiAgfVxufVxuXG4vKiogQmxvZyBMaXN0aW5nICoqL1xuLmJsb2ctbGlzdGluZyB7XG5cbiAgLmJyaWNrbGF5ZXItY29sdW1uIHtcbiAgICBwYWRkaW5nLWxlZnQ6IDBweDtcbiAgICBwYWRkaW5nLXJpZ2h0OiAyNXB4O1xuXG4gIH1cbiAgLmNhcmQge1xuICAgIG1hcmdpbi1ib3R0b206IDI1cHg7XG4gICAgYm9yZGVyOiAwO1xuICAgIGJveC1zaGFkb3c6IDAgMTBweCA0NXB4IC05cHggcmdiYSgwLDAsMCwwLjEpO1xuICB9XG5cbiAgLmNhcmQtZm9vdGVyIHtcbiAgICB0ZXh0LWFsaWduOiByaWdodDtcbiAgfVxuXG4gIC5ibG9nLWRhdGUge1xuICAgIGZvbnQtc2l6ZTogMTNweDtcbiAgfVxuXG4gIC5sYWJlbCB7XG5cbiAgfVxufVxuXG4vKiogQmxvZyBJdGVtICoqL1xuLmNvbnRlbnQtdGl0bGUge1xuICBtYXJnaW4tYm90dG9tOiAycmVtO1xuXG4gIGgyIHtcbiAgICBtYXJnaW4tYm90dG9tOiAwLjVyZW07XG4gIH1cbn1cblxuLmxhYmVsIHtcbiAgZm9udC1zaXplOiAxMnB4O1xuICB0ZXh0LXRyYW5zZm9ybTogdXBwZXJjYXNlO1xufVxuXG4vKiogUGFnaW5hdGlvbiAqKi9cbnVsLnBhZ2luYXRpb24ge1xuICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjtcbn1cblxuLnByZXYtbmV4dCB7XG4gIG1hcmdpbi10b3A6IDRyZW07XG59XG5cbi8qKiBTaWRlYmFyIHNwZWNpZmljIHR3ZWFrcyAqKi9cbiNzaWRlYmFyIHtcblxuICB1bC5yZWxhdGVkLXBhZ2VzIHtcbiAgICBib3gtc2hhZG93OiBub25lO1xuICAgIHBhZGRpbmc6IDA7XG4gICAgei1pbmRleDogMTtcblxuICAgIGxpIHtcbiAgICAgIGJvcmRlci1ib3R0b206IDFweCBzb2xpZCAkYm9yZGVyLWNvbG9yO1xuICAgICAgJjpsYXN0LWNoaWxkIHtcbiAgICAgICAgYm9yZGVyLWJvdHRvbTogMDtcbiAgICAgIH1cbiAgICB9XG5cbiAgfVxuXG4gIHVsLmFyY2hpdmVzIHtcbiAgICBsaXN0LXN0eWxlOiBub25lO1xuICAgIG1hcmdpbi1sZWZ0OiAwO1xuXG4gICAgLmxhYmVsIHtcbiAgICAgIHZlcnRpY2FsLWFsaWduOiB0ZXh0LXRvcDtcbiAgICB9XG4gIH1cbn1cbiIsIi5tb2R1bGFyLWhlcm8ge1xuICAjdG8tc3RhcnQge1xuICAgIGJvdHRvbTogMy41cmVtO1xuICB9XG59XG5cbi5tb2R1bGFyLWZlYXR1cmVzIHtcbiAgdGV4dC1hbGlnbjogY2VudGVyO1xuXG4gICYub2Zmc2V0LWJveCB7XG4gICAgLmZyYW1lLWJveCB7XG4gICAgICBtYXJnaW46IC0zcmVtICgtMXJlbSAtICRsYXlvdXQtc3BhY2luZykgM3JlbTtcbiAgICAgIHBhZGRpbmc6IDFyZW0gMXJlbTtcbiAgICAgIGJhY2tncm91bmQ6ICRsaWdodC1jb2xvcjtcbiAgICAgIGJveC1zaGFkb3c6IDAgMCA3NXB4IDAgcmdiYSgkZGFyay1jb2xvciwgMC4xKTtcbiAgICB9XG4gIH1cblxuICAmLnNtYWxsIHtcblxuICAgIC5jb2x1bW5zIHtcbiAgICAgIG1hcmdpbi10b3A6IC0xcmVtO1xuICAgIH1cblxuICAgIC5jb2x1bW46aG92ZXIge1xuICAgICAgLmZlYXR1cmUtaWNvbiBpIHtcbiAgICAgICAgY29sb3I6ICRwcmltYXJ5LWNvbG9yO1xuICAgICAgfVxuICAgIH1cblxuICAgIC5mZWF0dXJlLWljb24ge1xuICAgICAgZGlzcGxheTogYmxvY2s7XG4gICAgICBqdXN0aWZ5LWNvbnRlbnQ6IGxlZnQ7XG5cblxuICAgICAgaSB7XG4gICAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTtcbiAgICAgICAgZGlzcGxheTogaW5oZXJpdDtcbiAgICAgICAgZm9udC1zaXplOiA3MHB4O1xuICAgICAgICBtYXJnaW46IDAgYXV0byAxcmVtO1xuICAgICAgICB0cmFuc2Zvcm06IG5vbmU7XG4gICAgICAgIGxlZnQ6IGF1dG87XG4gICAgICAgIHRvcDogYXV0bztcbiAgICAgICAgY29sb3I6ICRncmF5LWNvbG9yO1xuICAgICAgICBAZXh0ZW5kIC5kZWZhdWx0LWFuaW1hdGlvbjtcblxuICAgICAgfVxuICAgICAgaDYge1xuICAgICAgICB0ZXh0LXRyYW5zZm9ybTogbm9uZTtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICAuZnJhbWUtYm94IHtcbiAgICBwYWRkaW5nOiAzcmVtIDA7XG5cbiAgICA+IHAge1xuICAgICAgbWF4LXdpZHRoOiA2MDBweDtcbiAgICAgIG1hcmdpbi1sZWZ0OiBhdXRvO1xuICAgICAgbWFyZ2luLXJpZ2h0OiBhdXRvO1xuICAgIH1cbiAgfVxuXG4gIC5jb2x1bW4ge1xuICAgIHBhZGRpbmc6IDFyZW07XG5cbiAgICAmOmhvdmVyIHtcbiAgICAgIC5mZWF0dXJlLWljb24ge1xuICAgICAgICBjb2xvcjogJGdyYXktY29sb3I7XG4gICAgICAgIGg2IHtcbiAgICAgICAgICBjb2xvcjogJHByaW1hcnktY29sb3I7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIC5mZWF0dXJlLWNvbnRlbnQge1xuICAgICAgICBjb2xvcjogJGdyYXktY29sb3ItZGFyaztcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICAuZmVhdHVyZS1pY29uIHtcbiAgICBmb250LXNpemU6IDEzMHB4O1xuICAgIGhlaWdodDogMTAwcHg7XG4gICAgY29sb3I6ICRncmF5LWNvbG9yLWxpZ2h0O1xuICAgIGRpc3BsYXk6IGZsZXg7XG4gICAgYWxpZ24taXRlbXM6IGNlbnRlcjtcbiAgICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjtcbiAgICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gICAgbWFyZ2luOiAxcmVtIDA7XG4gICAgQGV4dGVuZCAuZGVmYXVsdC1hbmltYXRpb247XG5cbiAgICBpIHtcbiAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICAgIGxlZnQ6IDUwJTtcbiAgICAgIHRvcDogNTAlO1xuICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGVYKC01MCUpIHRyYW5zbGF0ZVkoLTUwJSk7XG4gICAgfVxuXG4gICAgaDYge1xuICAgICAgYmFja2dyb3VuZDogJGxpZ2h0LWNvbG9yO1xuICAgICAgbGluZS1oZWlnaHQ6IDE7XG4gICAgICB6LWluZGV4OiAxO1xuICAgICAgdGV4dC10cmFuc2Zvcm06IHVwcGVyY2FzZTtcbiAgICAgIGZvbnQtd2VpZ2h0OiA2MDA7XG4gICAgICBtYXJnaW46IDA7XG4gICAgICBkaXNwbGF5OiBibG9jaztcbiAgICAgIGNvbG9yOiAkZ3JheS1jb2xvci1kYXJrO1xuICAgIH1cbiAgfVxuXG4gIC5mZWF0dXJlLWNvbnRlbnQge1xuICAgIGNvbG9yOiAkZ3JheS1jb2xvcjtcbiAgfVxufVxuXG4ubW9kdWxhci10ZXh0IHtcbiAgcGFkZGluZy10b3A6IDRyZW07XG4gIHBhZGRpbmctYm90dG9tOiA0cmVtO1xuXG4gIC5jb2x1bW5zLmxlZnQge1xuICAgIGZsZXgtZGlyZWN0aW9uOiByb3ctcmV2ZXJzZTtcbiAgfVxufSJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQWVBQSxBQUFBLElBQUksQ0FBQyxFQUNILE1BQU0sRUFBRSxJQUFJLEdBQ2I7O0FBRUQsQUFDRSxhQURXLENBQ1gsVUFBVSxDQUFDLEVBQ1QsT0FBTyxFZE1JLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxHY0x2Qjs7QUFHRCxBQUFBLGFBQWEsQ0FOZixhQUFhLENBTUssRUFDZCxXQUFXLEVkT08sSUFBSSxHY052Qjs7QUFHSCxBQUNFLGFBRFcsQ0FDWCxLQUFLLEdBQUcsTUFBTSxHQUFHLGFBQWEsQ0FBQyxFQUM3QixXQUFXLEVBQUUsQ0FBQyxHQUNmOztBQUdILEFBQUEsT0FBTyxBQUFBLFFBQVEsQ0FBQyxFQUNkLFlBQVksRWRYRSxJQUFJLEVjWWxCLGFBQWEsRWRaQyxJQUFJLEVjYWxCLFFBQVEsRUFBRSxRQUFRLEdBQ25COztBQUVELEFBQUEsY0FBYyxFQUFFLGFBQWEsRUFBRSx1QkFBdUIsRUFBRSxzQkFBc0IsQ0FBQyxFQUM3RSxPQUFPLEVBQUUsQ0FBQyxHQUNYOztBQUdELEFBQUEsS0FBSyxDQUFDLEVBQ0osT0FBTyxFQUFFLElBQUksRUFDYixXQUFXLEVBQUUsTUFBTSxFQUNuQixlQUFlLEVBQUUsTUFBTSxFQUV2QixXQUFXLEVBQUUsSUFBSSxFQUNqQixjQUFjLEVBQUUsSUFBSSxFQUNwQixlQUFlLEVBQUUsS0FBSyxFQUN0QixtQkFBbUIsRUFBRSxNQUFNLEdBZ0Y1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 */ diff --git a/user/themes/test/css-compiled/theme.min.css b/user/themes/test/css-compiled/theme.min.css new file mode 100644 index 0000000..036caa9 --- /dev/null +++ b/user/themes/test/css-compiled/theme.min.css @@ -0,0 +1 @@ +html{height:100%}#body-wrapper .container{padding:2rem 0 2rem}.header-fixed #body-wrapper{padding-top:4rem}.header-fixed .hero+#start>#body-wrapper{padding-top:0}section.section{position:relative;padding-right:1rem;padding-left:1rem}.overlay-dark,.overlay-dark-gradient,.overlay-light,.overlay-light-gradient{z-index:0}.hero{display:-ms-flexbox;display:flex;padding-top:6rem;padding-bottom:7rem;background-position:center;background-size:cover;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.hero h1{font-size:4rem;color:#242931}.hero h2{font-size:2.5rem;color:rgba(36,41,49,.8)}.hero.hero-fullscreen{min-height:100vh}.hero.hero-large{min-height:500px}.hero.hero-medium{min-height:400px}.hero.hero-small{min-height:110px}.hero.hero-tiny{min-height:8rem}.header-fixed .hero{background-position:50% 0}@media (max-width:840px){.hero h1{font-size:3rem}.hero h2{font-size:1.75rem}}@media (max-width:600px){.hero h1{font-size:2rem}.hero h2{font-size:1.25rem}}.hero.text-light h1{color:#fff}.hero.text-light h2{color:rgba(255,255,255,.8)}.hero p{font-size:.9rem;font-weight:300}.hero #to-start{font-size:2rem;position:absolute;bottom:10px;display:inline-block;cursor:pointer}.image-overlay{position:absolute;z-index:-1;top:0;right:0;bottom:0;left:0}.overlay-light .image-overlay{background:rgba(255,255,255,.4)}.overlay-light-gradient .image-overlay{background:linear-gradient(to bottom,rgba(255,255,255,.5),rgba(255,255,255,.2))}.overlay-dark .image-overlay{background:rgba(0,0,0,.4)}.overlay-dark-gradient .image-overlay{background:linear-gradient(to bottom,rgba(0,0,0,.5),rgba(0,0,0,.2))}html{font-size:16px}@media screen and (min-width:480px){html{font-size:calc(16px + 4 * ((100vw - 480px)/ 800))}}@media screen and (min-width:1280px){html{font-size:20px}}h1,h2,h3,h4,h5,h6{font-family:-apple-system,system-ui,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',sans-serif;margin-top:2rem;color:#3a414e}.h1,h1{font-size:3rem}.h2,h2{font-size:1.8rem}.h6,h6{font-weight:400}.title-center h1,.title-center h2{text-align:center}.title-h1h2 h1{font-weight:100;line-height:1.1;margin-bottom:0}.title-h1h2 h1 bold,.title-h1h2 h1 strong{font-weight:400}.title-h1h2 h1+h2{line-height:1.1;margin-top:0}.title-center h1+h2,.title-h1h2 h1+h2{font-weight:700;margin-bottom:50px}a:focus{outline:0!important;box-shadow:none!important}img{max-width:100%}.table table{width:100%;border-spacing:0;border-collapse:collapse}pre code,pre.xdebug-var-dump{line-height:1.5;display:block;overflow-x:auto;padding:1rem!important;color:inherit;border-radius:2px;background:#fafafa}pre[class*=language-] code{overflow-x:initial;padding:0!important;border-radius:inherit}pre code:not(.hljs):not([class*=language-]){background:#f8f8f8}i.fa.fa-heart-o.pulse,i.fa.fa-heart.pulse{color:#920}b,strong{font-weight:700}.heavy{font-weight:700}.light{font-weight:200}.text-light{color:rgba(255,255,255,.8)}.text-light h1,.text-light h2,.text-light h3,.text-light h4,.text-light h5,.text-light h6{color:rgba(255,255,255,.9)}#error{position:relative;margin-top:5rem;text-align:center}#error .icon{font-size:50px}#messages{margin-bottom:1rem}#messages .icon{font-size:1rem}ol,ul{margin-left:1.6rem}ol ol,ol ul,ul ol,ul ul{margin-left:1.6rem}ul{list-style:disc outside}ol{list-style:decimal outside}.notices{margin:1.5rem 0}.notices p{margin:1rem 0}form .button-wrapper{margin-top:.75rem;margin-bottom:1rem}form span.required{font-size:1.2rem;font-weight:700;color:#e85600}form .form-input[type=range]{-webkit-appearance:slider-horizontal;-moz-appearance:slider-horizontal;appearance:slider-horizontal}form .form-input[type=range]:focus{border:none;box-shadow:none}form .form-group:not(.form-field-toggleable) .checkboxes{display:inherit}form .form-group:not(.form-field-toggleable) .checkboxes label{display:inherit;margin:inherit;padding:.1rem .4rem .1rem 1.2rem}form .form-group:not(.form-field-toggleable) .checkboxes label:before{display:none}#grav-login>form{max-width:350px;margin:2rem auto 0}#grav-login .form-label{display:none}#grav-login .form-data{margin:1rem 0}#grav-login .form-input{text-align:center}#grav-login .button-wrapper{text-align:right}#grav-login .button-wrapper .form-data.rememberme{float:left;margin:0}#grav-login .login-form button[type=submit]{color:#fff;border-color:#227ded;background:#3085ee}#grav-login .login-form button[type=submit]:focus,#grav-login .login-form button[type=submit]:hover{color:#fff;border-color:#1370e3;background:#1877ec}#grav-login .login-form button[type=submit].active,#grav-login .login-form button[type=submit]:active{color:#fff;border-color:#126bd9;background:#1372e7}#grav-login .twofa-form button[type=submit]:first-child{float:right;margin-left:4px;color:#fff;border-color:#227ded;background:#3085ee}#grav-login .twofa-form button[type=submit]:first-child:focus,#grav-login .twofa-form button[type=submit]:first-child:hover{color:#fff;border-color:#1370e3;background:#1877ec}#grav-login .twofa-form button[type=submit]:first-child.active,#grav-login .twofa-form button[type=submit]:first-child:active{color:#fff;border-color:#126bd9;background:#1372e7}.mobile-container{position:absolute;z-index:2;top:40%;left:0;margin:0 auto}.mobile-logo img,.mobile-logo svg{height:42px;margin-top:.7rem;margin-left:1.4rem}.mobile-logo img path,.mobile-logo svg path{fill:#fff}.mobile-menu{z-index:3;top:0;right:0;display:none}.header-fixed .mobile-menu{position:fixed}@media (max-width:840px){.mobile-menu{display:block}}.mobile-menu .button_container{position:absolute;z-index:100;top:1.3rem;right:1rem;width:28px;height:24px;cursor:pointer;transition:opacity .25s ease,top .5s ease}.mobile-menu .button_container:hover{opacity:.7}.mobile-menu .button_container.active{position:fixed}.mobile-menu .button_container.active .top{transform:translateY(8px) translateX(0) rotate(45deg);background:#fff}.mobile-menu .button_container.active .middle{opacity:0;background:#fff}.mobile-menu .button_container.active .bottom{transform:translateY(-8px) translateX(0) rotate(-45deg);background:#fff}.mobile-menu .button_container span{position:absolute;top:0;left:0;width:100%;height:4px;cursor:pointer;transition:all .35s ease;border:none;background:#3085ee}.mobile-menu .button_container span:nth-of-type(2){top:8px}.mobile-menu .button_container span:nth-of-type(3){top:16px}.overlay{position:fixed;top:0;left:0;visibility:hidden;width:100%;height:0;transition:opacity .35s,visibility .35s,height .35s;opacity:0;background:#000}.overlay.open{visibility:visible;height:100%;opacity:.95}.overlay nav{position:relative;margin:0 auto;text-align:center}.overlay-menu{overflow-y:scroll;height:calc(100% - 90px)}.overlay-menu>.tree{text-align:left}.treemenu.treemenu-root{margin:1rem}.treemenu li{line-height:1.2rem;margin:0 0 1px;padding:5px 0;list-style:none;background:rgba(102,113,137,.1)}.treemenu li a{font-size:1rem;display:block;margin-left:1.2rem}.treemenu li a.active,.treemenu li a:focus,.treemenu li a:hover{text-decoration:none;color:#3e8def!important}.treemenu ul{margin:0 0 0 1rem}.treemenu .toggler{font-size:1.1rem;line-height:1rem;float:left;padding-left:5px;cursor:pointer;vertical-align:top}.treemenu .toggler:before{display:inline-block;margin-right:2pt}.treemenu li.tree-empty>.toggler{cursor:default;opacity:.3}.treemenu li.tree-empty>.toggler:before{content:'\2022'}.treemenu li.tree-closed>.toggler:before{content:'+'}.treemenu li.tree-opened>.toggler:before{content:'\2212'}.mobile-nav-open{overflow-y:hidden}#header,#header .logo img,#header .logo svg,#header .navbar-section,.default-animation,.modular-features .feature-icon,.modular-features.small .feature-icon i{transition:all .5s ease}.pulse{transform-origin:70% 70%;animation-name:pulse_animation;animation-duration:2s;animation-timing-function:linear;animation-iteration-count:infinite}@keyframes pulse_animation{0%{transform:scale(1)}30%{transform:scale(1)}40%{transform:scale(1.08)}50%{transform:scale(1)}60%{transform:scale(1)}70%{transform:scale(1.05)}80%{transform:scale(1)}100%{transform:scale(1)}}#header{font-size:.7rem;font-weight:700;width:100%;height:4rem;color:#454d5d;border-bottom:1px solid rgba(172,179,194,.2);background:#fff}#header a{color:#454d5d}#header .logo svg path{fill:#222}.header-dark #header:not(.scrolled){color:#fff;background:#222}.header-dark #header:not(.scrolled) a{color:rgba(255,255,255,.7)!important}.header-dark #header:not(.scrolled) a.active{color:#fff!important}.header-dark #header:not(.scrolled) .dropmenu ul ul a{color:#454d5d!important}.header-dark #header:not(.scrolled) .logo svg path{fill:#fff}.header-dark.header-transparent #header:not(.scrolled){background:rgba(0,0,0,.05)}.header-transparent #header:not(.scrolled){background:rgba(255,255,255,.05)}#header .navbar-section{height:4rem}@media (max-width:840px){#header .navbar-section{margin-right:2rem}}@media (max-width:840px){#header .navbar-section.desktop-menu{display:none}}#header .logo img,#header .logo svg{display:inherit;height:42px}.header-fixed #header{position:fixed;z-index:2;top:0}body.header-fixed.header-animated #header.scrolled{height:2.3rem}body.header-fixed.header-animated #header.scrolled .navbar-section{height:2.3rem}body.header-fixed.header-animated #header.scrolled .logo img,body.header-fixed.header-animated #header.scrolled .logo svg{height:28px}body.header-fixed.header-animated #header.scrolled~.mobile-menu .button_container{top:.5rem}.login-status-wrapper{white-space:nowrap}body.sticky-footer{display:-ms-flexbox;display:flex;flex-direction:column;height:100%;min-height:100vh;-ms-flex-direction:column}body.sticky-footer #page-wrapper{-ms-flex:1 0 auto;flex:1 0 auto}#footer{padding:1rem 1rem 0;text-align:center;color:#acb3c2}@media (max-width:840px){.dropmenu{display:none}}.dropmenu ul{display:-ms-flexbox;display:flex;margin:0;white-space:nowrap}.dropmenu ul li{position:relative;margin:0}.dropmenu ul li a{display:block;padding:7px 30px 7px 20px;text-decoration:none}.dropmenu ul li a.active,.dropmenu ul li a:focus,.dropmenu ul li a:hover{color:#3085ee!important}.dropmenu ul li a:before{font-family:FontAwesome;display:inline-block;float:right;margin-right:-20px;content:'\f107';vertical-align:middle}.dropmenu ul li a:only-child{padding-right:20px}.dropmenu ul li a:only-child:before{content:''}.dropmenu ul li:hover>ul{display:block;visibility:visible}.dropmenu ul ul li a:before{content:'\f105'}.dropmenu ul ul{position:absolute;top:100%;visibility:hidden;list-style:none;background:#fff;box-shadow:0 3px 5px rgba(0,0,0,.1)}.dropmenu ul ul ul{position:absolute;top:0;left:100%}.dropmenu>ul>li{display:inline-block}.dropmenu.animated ul li{transition:background .7s,color .5s}.dropmenu.animated ul li:hover>ul{transform:translateY(0);opacity:1}.dropmenu.animated ul ul{transition:transform .3s,opacity .5s;transform:translateY(-10px);opacity:0}.extra-spacing:not(.col-12),:not(.col12)>.e-content{padding-right:1rem}@media (max-width:840px){.extra-spacing:not(.col-12),:not(.col12)>.e-content{padding-right:0}}#breadcrumbs{display:-ms-flexbox;display:flex;margin-top:-1rem;margin-bottom:1rem;padding-left:0;-ms-flex-align:center;align-items:center}#breadcrumbs i{display:none}#breadcrumbs a,#breadcrumbs span{padding:0 .5rem}#breadcrumbs a:first-child,#breadcrumbs span:first-child{padding-left:0}#breadcrumbs a:not(:first-child)::before,#breadcrumbs span:not(:first-child)::before{padding-right:1rem;content:'/';color:#e7e9ed}.blog-listing .bricklayer-column{padding-right:25px;padding-left:0}.blog-listing .card{margin-bottom:25px;border:0;box-shadow:0 10px 45px -9px rgba(0,0,0,.1)}.blog-listing .card-footer{text-align:right}.blog-listing .blog-date{font-size:13px}.content-title{margin-bottom:2rem}.content-title h2{margin-bottom:.5rem}.label{font-size:12px;text-transform:uppercase}ul.pagination{-ms-flex-pack:center;justify-content:center}.prev-next{margin-top:4rem}#sidebar ul.related-pages{z-index:1;padding:0;box-shadow:none}#sidebar ul.related-pages li{border-bottom:1px solid #e7e9ed}#sidebar ul.related-pages li:last-child{border-bottom:0}#sidebar ul.archives{margin-left:0;list-style:none}#sidebar ul.archives .label{vertical-align:text-top}.modular-hero #to-start{bottom:3.5rem}.modular-features{text-align:center}.modular-features.offset-box .frame-box{margin:-3rem -1.4rem 3rem;padding:1rem 1rem;background:#fff;box-shadow:0 0 75px 0 rgba(69,77,93,.1)}.modular-features.small .columns{margin-top:-1rem}.modular-features.small .column:hover .feature-icon i{color:#3085ee}.modular-features.small .feature-icon{display:block;-ms-flex-pack:left;justify-content:left}.modular-features.small .feature-icon i{font-size:70px;position:relative;top:auto;left:auto;display:inherit;margin:0 auto 1rem;transform:none;color:#acb3c2}.modular-features.small .feature-icon h6{text-transform:none}.modular-features .frame-box{padding:3rem 0}.modular-features .frame-box>p{max-width:600px;margin-right:auto;margin-left:auto}.modular-features .column{padding:1rem}.modular-features .column:hover .feature-icon{color:#acb3c2}.modular-features .column:hover .feature-icon h6{color:#3085ee}.modular-features .column:hover .feature-content{color:#667189}.modular-features .feature-icon{font-size:130px;position:relative;display:-ms-flexbox;display:flex;height:100px;margin:1rem 0;color:#e7e9ed;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.modular-features .feature-icon i{position:absolute;top:50%;left:50%;transform:translateX(-50%) translateY(-50%)}.modular-features .feature-icon h6{font-weight:600;line-height:1;z-index:1;display:block;margin:0;text-transform:uppercase;color:#667189;background:#fff}.modular-features .feature-content{color:#acb3c2}.modular-text{padding-top:4rem;padding-bottom:4rem}.modular-text .columns.left{flex-direction:row-reverse;-ms-flex-direction:row-reverse} \ No newline at end of file diff --git a/user/themes/test/css/bricklayer.css b/user/themes/test/css/bricklayer.css new file mode 100755 index 0000000..4505480 --- /dev/null +++ b/user/themes/test/css/bricklayer.css @@ -0,0 +1,49 @@ +.bricklayer { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-align: start; + -webkit-align-items: flex-start; + -ms-flex-align: start; + align-items: flex-start; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-flex-wrap: wrap; + -ms-flex-wrap: wrap; + flex-wrap: wrap; +} + +.bricklayer-column-sizer { + width: 100%; + display: none; +} + +@media screen and (min-width: 640px) { + .bricklayer-column-sizer { + width: 100%; + } +} + +@media screen and (min-width: 980px) { + .bricklayer-column-sizer { + width: 50%; + } +} + +/*@media screen and (min-width: 1200px) {*/ + /*.bricklayer-column-sizer {*/ + /*width: 33.33333%;*/ + /*}*/ +/*}*/ + +.bricklayer-column { + -webkit-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + padding-left: 5px; + padding-right: 5px; +} \ No newline at end of file diff --git a/user/themes/test/css/custom.css b/user/themes/test/css/custom.css new file mode 100644 index 0000000..c3f04b2 --- /dev/null +++ b/user/themes/test/css/custom.css @@ -0,0 +1,27 @@ +img[src^="/user/pages/01.home/oeil.gif"]{ +width: 200px; +height: 200px; +position: absolute; +top: 50px; +left: 50px; +} + +.header-fixed #body-wrapper { + margin: 0; + padding: 0; +} + +#body-wrapper { + margin: 0; + padding: 0; +} + +#body-wrapper .container { + margin: 0; + padding: 0; +} + +#start { + margin: 0; + padding: 0; +} diff --git a/user/themes/test/css/line-awesome.min.css b/user/themes/test/css/line-awesome.min.css new file mode 100644 index 0000000..49178de --- /dev/null +++ b/user/themes/test/css/line-awesome.min.css @@ -0,0 +1,4 @@ +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */.fa.fa-pull-left,.fa.pull-left{margin-right:.3em}.fa,.fa-stack{display:inline-block}.fa-fw,.fa-li{text-align:center}@font-face{font-family:FontAwesome;src:url(../fonts/line-awesome.eot?v=4.7.0);src:url(../fonts/line-awesome.eot?#iefix&v=4.7.0) format('embedded-opentype'),url(../fonts/line-awesome.woff2?v=4.7.0) format('woff2'),url(../fonts/line-awesome.woff?v=4.7.0) format('woff'),url(../fonts/line-awesome.ttf?v=4.7.0) format('truetype'),url(../fonts/line-awesome.svg?v=4.7.0#fontawesomeregular) format('svg');font-weight:400;font-style:normal}.fa{font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa.fa-pull-right,.fa.pull-right{margin-left:.3em}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right,.pull-right{float:right}.pull-left{float:left}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1,-1);-ms-transform:scale(1,-1);transform:scale(1,-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-rotate-90{filter:none}.fa-stack{position:relative;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-close:before,.fa-remove:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-cog:before,.fa-gear:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-repeat:before,.fa-rotate-right:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-exclamation-triangle:before,.fa-warning:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-cogs:before,.fa-gears:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-floppy-o:before,.fa-save:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-sort:before,.fa-unsorted:before{content:"\f0dc"}.fa-sort-desc:before,.fa-sort-down:before{content:"\f0dd"}.fa-sort-asc:before,.fa-sort-up:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-gavel:before,.fa-legal:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-bolt:before,.fa-flash:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-clipboard:before,.fa-paste:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-chain-broken:before,.fa-unlink:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:"\f150"}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:"\f151"}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:"\f152"}.fa-eur:before,.fa-euro:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-inr:before,.fa-rupee:before{content:"\f156"}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:"\f157"}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:"\f158"}.fa-krw:before,.fa-won:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-try:before,.fa-turkish-lira:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-bank:before,.fa-institution:before,.fa-university:before{content:"\f19c"}.fa-graduation-cap:before,.fa-mortar-board:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:"\f1c5"}.fa-file-archive-o:before,.fa-file-zip-o:before{content:"\f1c6"}.fa-file-audio-o:before,.fa-file-sound-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:"\f1d0"}.fa-empire:before,.fa-ge:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-paper-plane:before,.fa-send:before{content:"\f1d8"}.fa-paper-plane-o:before,.fa-send-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-bed:before,.fa-hotel:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-y-combinator:before,.fa-yc:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-television:before,.fa-tv:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:"\f2a3"}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-sign-language:before,.fa-signing:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-address-card:before,.fa-vcard:before{content:"\f2bb"}.fa-address-card-o:before,.fa-vcard-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} \ No newline at end of file diff --git a/user/themes/test/fonts/line-awesome.eot b/user/themes/test/fonts/line-awesome.eot new file mode 100644 index 0000000000000000000000000000000000000000..f13ae4a4efe61e68b715b82e226a77f344f63aaa GIT binary patch literal 213245 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


    A!3)rViw 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 literal 0 HcmV?d00001 diff --git a/user/themes/test/fonts/line-awesome.svg b/user/themes/test/fonts/line-awesome.svg new file mode 100644 index 0000000..21c3c41 --- /dev/null +++ b/user/themes/test/fonts/line-awesome.svg @@ -0,0 +1,2954 @@ + + + + +Created by FontForge 20120731 at Fri Nov 24 02:04:36 2017 + By www-data +SIL Open Font License + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/user/themes/test/fonts/line-awesome.ttf b/user/themes/test/fonts/line-awesome.ttf new file mode 100644 index 0000000000000000000000000000000000000000..afdb6877858f68ee790a2f11a56e8ebca9d9eced GIT binary patch literal 263504 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�|aTVc1Jz>+Zwc!6>IYm7T4 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%0D!Ef4E-=Qp|8ZL6Ww^DZ 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%0Dy`K@ePfBKQf>13XmRxQg;ctZCi#Z7M}PF@v1?*w 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 A#sB~S literal 0 HcmV?d00001 diff --git a/user/themes/test/fonts/line-awesome.woff b/user/themes/test/fonts/line-awesome.woff new file mode 100644 index 0000000000000000000000000000000000000000..8897d78325a47bc33a31f14267b6b5f2c9d80362 GIT binary patch literal 117372 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

    }VC>>`p6$(nP+)^SkmwNx}e?9uEs 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 literal 0 HcmV?d00001 diff --git a/user/themes/test/gulpfile.js b/user/themes/test/gulpfile.js new file mode 100755 index 0000000..74e7bed --- /dev/null +++ b/user/themes/test/gulpfile.js @@ -0,0 +1,43 @@ +var gulp = require('gulp'); +var sass = require('gulp-sass'); +var cleancss = require('gulp-clean-css'); +var csscomb = require('gulp-csscomb'); +var rename = require('gulp-rename'); +var autoprefixer = require('gulp-autoprefixer'); +var sourcemaps = require('gulp-sourcemaps'); + +// configure the paths +var watch_dir = './scss/**/*.scss'; +var src_dir = './scss/*.scss'; +var dest_dir = './css-compiled'; + +var paths = { + source: src_dir +}; + +function watch() { + return gulp.watch(watch_dir, build); +} + +function build() { + return gulp.src(paths.source) + .pipe(sourcemaps.init()) + .pipe(sass({ + outputStyle: 'compact', + precision: 10 + }).on('error', sass.logError) + ) + .pipe(sourcemaps.write()) + .pipe(autoprefixer()) + .pipe(gulp.dest(dest_dir)) + .pipe(csscomb()) + .pipe(cleancss()) + .pipe(rename({ + suffix: '.min' + })) + .pipe(gulp.dest(dest_dir)); +} + +exports.watch = watch; +exports.build = build; +exports.default = build; diff --git a/user/themes/test/images/favicon.png b/user/themes/test/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..85526cf43dc3d1557e0b1f6b19ba037861ec8d8e GIT binary patch literal 13203 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 FV1g`BrzZda literal 0 HcmV?d00001 diff --git a/user/themes/test/images/grav-logo.svg b/user/themes/test/images/grav-logo.svg new file mode 100644 index 0000000..845a994 --- /dev/null +++ b/user/themes/test/images/grav-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/user/themes/test/images/logo/.gitkeep b/user/themes/test/images/logo/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/user/themes/test/images/logo/oeil.gif b/user/themes/test/images/logo/oeil.gif new file mode 100644 index 0000000000000000000000000000000000000000..97e67b844c79275679e0f64e21e3539330b26dd2 GIT binary patch literal 53699 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 literal 0 HcmV?d00001 diff --git a/user/themes/test/scss/spectre-exp.scss b/user/themes/test/scss/spectre-exp.scss new file mode 100755 index 0000000..a2813b4 --- /dev/null +++ b/user/themes/test/scss/spectre-exp.scss @@ -0,0 +1,19 @@ +// Variables and mixins +@import "theme/variables"; +@import "spectre/variables"; +@import "spectre/mixins"; + +/*! Spectre.css Experimentals v#{$version} | MIT License | github.com/picturepan2/spectre */ +// Experimentals +@import "spectre/autocomplete"; +@import "spectre/calendars"; +@import "spectre/carousels"; +@import "spectre/comparison-sliders"; +@import "spectre/filters"; +@import "spectre/meters"; +@import "spectre/off-canvas"; +@import "spectre/parallax"; +@import "spectre/progress"; +@import "spectre/sliders"; +@import "spectre/timelines"; +@import "spectre/viewer-360"; diff --git a/user/themes/test/scss/spectre-icons.scss b/user/themes/test/scss/spectre-icons.scss new file mode 100755 index 0000000..a223397 --- /dev/null +++ b/user/themes/test/scss/spectre-icons.scss @@ -0,0 +1,11 @@ +// Variables and mixins +@import "theme/variables"; +@import "spectre/variables"; +@import "spectre/mixins"; + +/*! Spectre.css Icons v#{$version} | MIT License | github.com/picturepan2/spectre */ +// Icons +@import "spectre/icons/icons-core"; +@import "spectre/icons/icons-navigation"; +@import "spectre/icons/icons-action"; +@import "spectre/icons/icons-object"; diff --git a/user/themes/test/scss/spectre.scss b/user/themes/test/scss/spectre.scss new file mode 100755 index 0000000..7bc46cc --- /dev/null +++ b/user/themes/test/scss/spectre.scss @@ -0,0 +1,53 @@ +// Variables and mixins +@import "theme/variables"; +@import "spectre/variables"; +@import "spectre/mixins"; + +/*! Spectre.css v#{$version} | MIT License | github.com/picturepan2/spectre */ +// Reset and dependencies +@import "spectre/normalize"; +@import "spectre/base"; + +// Elements +@import "spectre/typography"; +@import "spectre/asian"; +@import "spectre/tables"; +@import "spectre/buttons"; +@import "spectre/forms"; +@import "spectre/labels"; +@import "spectre/codes"; +@import "spectre/media"; + +// Layout +@import "spectre/layout"; +@import "spectre/hero"; +@import "spectre/navbar"; + +// Components +@import "spectre/accordions"; +@import "spectre/avatars"; +@import "spectre/badges"; +@import "spectre/breadcrumbs"; +@import "spectre/bars"; +@import "spectre/cards"; +@import "spectre/chips"; +@import "spectre/dropdowns"; +@import "spectre/empty"; +@import "spectre/menus"; +@import "spectre/modals"; +@import "spectre/navs"; +@import "spectre/pagination"; +@import "spectre/panels"; +@import "spectre/popovers"; +@import "spectre/steps"; +@import "spectre/tabs"; +@import "spectre/tiles"; +@import "spectre/toasts"; +@import "spectre/tooltips"; + +// Utility classes +@import "spectre/animations"; +@import "spectre/utilities"; + +// Extras +@import "theme/extensions"; diff --git a/user/themes/test/scss/spectre/_accordions.scss b/user/themes/test/scss/spectre/_accordions.scss new file mode 100755 index 0000000..fd21585 --- /dev/null +++ b/user/themes/test/scss/spectre/_accordions.scss @@ -0,0 +1,38 @@ +// Accordions +.accordion { + input:checked ~, + &[open] { + & .accordion-header { + .icon { + transform: rotate(90deg); + } + } + + & .accordion-body { + max-height: 50rem; + } + } + + .accordion-header { + display: block; + padding: $unit-1 $unit-2; + + .icon { + transition: transform .25s; + } + } + + .accordion-body { + margin-bottom: $layout-spacing; + max-height: 0; + overflow: hidden; + transition: max-height .25s; + } +} + +// Remove default details marker in Webkit +summary.accordion-header { + &::-webkit-details-marker { + display: none; + } +} diff --git a/user/themes/test/scss/spectre/_animations.scss b/user/themes/test/scss/spectre/_animations.scss new file mode 100755 index 0000000..e7fde1a --- /dev/null +++ b/user/themes/test/scss/spectre/_animations.scss @@ -0,0 +1,20 @@ +// Animations +@keyframes loading { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +@keyframes slide-down { + 0% { + opacity: 0; + transform: translateY(-$unit-8); + } + 100% { + opacity: 1; + transform: translateY(0); + } +} diff --git a/user/themes/test/scss/spectre/_asian.scss b/user/themes/test/scss/spectre/_asian.scss new file mode 100755 index 0000000..e426f39 --- /dev/null +++ b/user/themes/test/scss/spectre/_asian.scss @@ -0,0 +1,43 @@ +// Optimized for East Asian CJK +html:lang(zh), +html:lang(zh-Hans), +.lang-zh, +.lang-zh-hans { + font-family: $cjk-zh-hans-font-family; +} + +html:lang(zh-Hant), +.lang-zh-hant { + font-family: $cjk-zh-hant-font-family; +} + +html:lang(ja), +.lang-ja { + font-family: $cjk-jp-font-family; +} + +html:lang(ko), +.lang-ko { + font-family: $cjk-ko-font-family; +} + +:lang(zh), +:lang(ja), +.lang-cjk { + ins, + u { + border-bottom: $border-width solid; + text-decoration: none; + } + + del + del, + del + s, + ins + ins, + ins + u, + s + del, + s + s, + u + ins, + u + u { + margin-left: .125em; + } +} diff --git a/user/themes/test/scss/spectre/_autocomplete.scss b/user/themes/test/scss/spectre/_autocomplete.scss new file mode 100755 index 0000000..279fa03 --- /dev/null +++ b/user/themes/test/scss/spectre/_autocomplete.scss @@ -0,0 +1,47 @@ +// Autocomplete +.form-autocomplete { + position: relative; + + .form-autocomplete-input { + align-content: flex-start; + display: flex; + flex-wrap: wrap; + height: auto; + min-height: $unit-8; + padding: $unit-h; + + &.is-focused { + @include control-shadow(); + border-color: $primary-color; + } + + .form-input { + border-color: transparent; + box-shadow: none; + display: inline-block; + flex: 1 0 auto; + height: $unit-6; + line-height: $unit-4; + margin: $unit-h; + width: auto; + } + } + + .menu { + left: 0; + position: absolute; + top: 100%; + width: 100%; + } + + &.autocomplete-oneline { + .form-autocomplete-input { + flex-wrap: nowrap; + overflow-x: auto; + } + + .chip { + flex: 1 0 auto; + } + } +} diff --git a/user/themes/test/scss/spectre/_avatars.scss b/user/themes/test/scss/spectre/_avatars.scss new file mode 100755 index 0000000..b203aa2 --- /dev/null +++ b/user/themes/test/scss/spectre/_avatars.scss @@ -0,0 +1,77 @@ +// Avatars +.avatar { + @include avatar-base(); + background: $primary-color; + border-radius: 50%; + color: rgba($light-color, .85); + display: inline-block; + font-weight: 300; + line-height: 1.25; + margin: 0; + position: relative; + vertical-align: middle; + + &.avatar-xs { + @include avatar-base($unit-4); + } + &.avatar-sm { + @include avatar-base($unit-6); + } + &.avatar-lg { + @include avatar-base($unit-12); + } + &.avatar-xl { + @include avatar-base($unit-16); + } + + img { + border-radius: 50%; + height: 100%; + position: relative; + width: 100%; + z-index: $zindex-0; + } + + .avatar-icon, + .avatar-presence { + background: $bg-color-light; + bottom: 14.64%; + height: 50%; + padding: $border-width-lg; + position: absolute; + right: 14.64%; + transform: translate(50%, 50%); + width: 50%; + z-index: $zindex-0 + 1; + } + + .avatar-presence { + background: $gray-color; + box-shadow: 0 0 0 $border-width-lg $light-color; + border-radius: 50%; + height: .5em; + width: .5em; + + &.online { + background: $success-color; + } + + &.busy { + background: $error-color; + } + + &.away { + background: $warning-color; + } + } + + &[data-initial]::before { + color: currentColor; + content: attr(data-initial); + left: 50%; + position: absolute; + top: 50%; + transform: translate(-50%, -50%); + z-index: $zindex-0; + } +} \ No newline at end of file diff --git a/user/themes/test/scss/spectre/_badges.scss b/user/themes/test/scss/spectre/_badges.scss new file mode 100755 index 0000000..d67f6d1 --- /dev/null +++ b/user/themes/test/scss/spectre/_badges.scss @@ -0,0 +1,60 @@ +// Badges +.badge { + position: relative; + white-space: nowrap; + + &[data-badge], + &:not([data-badge]) { + &::after { + background: $primary-color; + background-clip: padding-box; + border-radius: .5rem; + box-shadow: 0 0 0 .1rem $bg-color-light; + color: $light-color; + content: attr(data-badge); + display: inline-block; + transform: translate(-.05rem, -.5rem); + } + } + &[data-badge] { + &::after { + font-size: $font-size-sm; + height: .9rem; + line-height: 1; + min-width: .9rem; + padding: .1rem .2rem; + text-align: center; + white-space: nowrap; + } + } + &:not([data-badge]), + &[data-badge=""] { + &::after { + height: 6px; + min-width: 6px; + padding: 0; + width: 6px; + } + } + + // Badges for Buttons + &.btn { + &::after { + position: absolute; + top: 0; + right: 0; + transform: translate(50%, -50%); + } + } + + // Badges for Avatars + &.avatar { + &::after { + position: absolute; + top: 14.64%; + right: 14.64%; + transform: translate(50%, -50%); + z-index: $zindex-1; + } + } +} diff --git a/user/themes/test/scss/spectre/_bars.scss b/user/themes/test/scss/spectre/_bars.scss new file mode 100755 index 0000000..47e21c9 --- /dev/null +++ b/user/themes/test/scss/spectre/_bars.scss @@ -0,0 +1,71 @@ +// Bars +.bar { + background: $bg-color-dark; + border-radius: $border-radius; + display: flex; + flex-wrap: nowrap; + height: $unit-4; + width: 100%; + + &.bar-sm { + height: $unit-1; + } + + // TODO: attr() support + .bar-item { + background: $primary-color; + color: $light-color; + display: block; + font-size: $font-size-sm; + flex-shrink: 0; + line-height: $unit-4; + height: 100%; + position: relative; + text-align: center; + width: 0; + + &:first-child { + border-bottom-left-radius: $border-radius; + border-top-left-radius: $border-radius; + } + &:last-child { + border-bottom-right-radius: $border-radius; + border-top-right-radius: $border-radius; + flex-shrink: 1; + } + } +} + +// Slider bar +.bar-slider { + height: $border-width-lg; + margin: $layout-spacing 0; + position: relative; + + .bar-item { + left: 0; + padding: 0; + position: absolute; + &:not(:last-child):first-child { + background: $bg-color-dark; + z-index: $zindex-0; + } + } + + .bar-slider-btn { + background: $primary-color; + border: 0; + border-radius: 50%; + height: $unit-3; + padding: 0; + position: absolute; + right: 0; + top: 50%; + transform: translate(50%, -50%); + width: $unit-3; + + &:active { + box-shadow: 0 0 0 .1rem $primary-color; + } + } +} diff --git a/user/themes/test/scss/spectre/_base.scss b/user/themes/test/scss/spectre/_base.scss new file mode 100755 index 0000000..4e01b20 --- /dev/null +++ b/user/themes/test/scss/spectre/_base.scss @@ -0,0 +1,44 @@ +// Base +*, +*::before, +*::after { + box-sizing: inherit; +} + +html { + box-sizing: border-box; + font-size: $html-font-size; + line-height: $html-line-height; + -webkit-tap-highlight-color: transparent; +} + +body { + background: $body-bg; + color: $body-font-color; + font-family: $body-font-family; + font-size: $font-size; + overflow-x: hidden; + text-rendering: optimizeLegibility; +} + +a { + color: $link-color; + outline: none; + text-decoration: none; + + &:focus { + @include control-shadow(); + } + + &:focus, + &:hover, + &:active, + &.active { + color: $link-color-dark; + text-decoration: underline; + } + + &:visited { + color: $link-color-light; + } +} diff --git a/user/themes/test/scss/spectre/_breadcrumbs.scss b/user/themes/test/scss/spectre/_breadcrumbs.scss new file mode 100755 index 0000000..6a5af31 --- /dev/null +++ b/user/themes/test/scss/spectre/_breadcrumbs.scss @@ -0,0 +1,29 @@ +// Breadcrumbs +.breadcrumb { + list-style: none; + margin: $unit-1 0; + padding: $unit-1 0; + + .breadcrumb-item { + color: $gray-color-dark; + display: inline-block; + margin: 0; + padding: $unit-1 0; + + &:not(:last-child) { + margin-right: $unit-1; + + a { + color: $gray-color-dark; + } + } + + &:not(:first-child) { + &::before { + color: $gray-color-dark; + content: "/"; + padding-right: $unit-2; + } + } + } +} diff --git a/user/themes/test/scss/spectre/_buttons.scss b/user/themes/test/scss/spectre/_buttons.scss new file mode 100755 index 0000000..9158f0f --- /dev/null +++ b/user/themes/test/scss/spectre/_buttons.scss @@ -0,0 +1,193 @@ +// Buttons +.btn { + appearance: none; + background: $bg-color-light; + border: $border-width solid $primary-color; + border-radius: $border-radius; + color: $primary-color; + cursor: pointer; + display: inline-block; + font-size: $font-size; + height: $control-size; + line-height: $line-height; + outline: none; + padding: $control-padding-y $control-padding-x; + text-align: center; + text-decoration: none; + transition: background .2s, border .2s, box-shadow .2s, color .2s; + user-select: none; + vertical-align: middle; + white-space: nowrap; + &:focus { + @include control-shadow(); + } + &:focus, + &:hover { + background: $secondary-color; + border-color: $primary-color-dark; + text-decoration: none; + } + &:active, + &.active { + background: $primary-color-dark; + border-color: darken($primary-color-dark, 5%); + color: $light-color; + text-decoration: none; + &.loading { + &::after { + border-bottom-color: $light-color; + border-left-color: $light-color; + } + } + } + &[disabled], + &:disabled, + &.disabled { + cursor: default; + opacity: .5; + pointer-events: none; + } + + // Button Primary + &.btn-primary { + background: $primary-color; + border-color: $primary-color-dark; + color: $light-color; + &:focus, + &:hover { + background: darken($primary-color-dark, 2%); + border-color: darken($primary-color-dark, 5%); + color: $light-color; + } + &:active, + &.active { + background: darken($primary-color-dark, 4%); + border-color: darken($primary-color-dark, 7%); + color: $light-color; + } + &.loading { + &::after { + border-bottom-color: $light-color; + border-left-color: $light-color; + } + } + } + + // Button Colors + &.btn-success { + @include button-variant($success-color); + } + + &.btn-error { + @include button-variant($error-color); + } + + // Button Link + &.btn-link { + background: transparent; + border-color: transparent; + color: $link-color; + &:focus, + &:hover, + &:active, + &.active { + color: $link-color-dark; + } + } + + // Button Sizes + &.btn-sm { + font-size: $font-size-sm; + height: $control-size-sm; + padding: $control-padding-y-sm $control-padding-x-sm; + } + + &.btn-lg { + font-size: $font-size-lg; + height: $control-size-lg; + padding: $control-padding-y-lg $control-padding-x-lg; + } + + // Button Block + &.btn-block { + display: block; + width: 100%; + } + + // Button Action + &.btn-action { + width: $control-size; + padding-left: 0; + padding-right: 0; + + &.btn-sm { + width: $control-size-sm; + } + + &.btn-lg { + width: $control-size-lg; + } + } + + // Button Clear + &.btn-clear { + background: transparent; + border: 0; + color: currentColor; + height: $unit-5; + line-height: $unit-4; + margin-left: $unit-1; + margin-right: -2px; + opacity: 1; + padding: $unit-h; + text-decoration: none; + width: $unit-5; + + &:focus, + &:hover { + background: rgba($bg-color, .5); + opacity: .95; + } + + &::before { + content: "\2715"; + } + } +} + +// Button groups +.btn-group { + display: inline-flex; + flex-wrap: wrap; + + .btn { + flex: 1 0 auto; + &:first-child:not(:last-child) { + border-bottom-right-radius: 0; + border-top-right-radius: 0; + } + &:not(:first-child):not(:last-child) { + border-radius: 0; + margin-left: -$border-width; + } + &:last-child:not(:first-child) { + border-bottom-left-radius: 0; + border-top-left-radius: 0; + margin-left: -$border-width; + } + &:focus, + &:hover, + &:active, + &.active { + z-index: $zindex-0; + } + } + + &.btn-group-block { + display: flex; + + .btn { + flex: 1 0 0; + } + } +} diff --git a/user/themes/test/scss/spectre/_calendars.scss b/user/themes/test/scss/spectre/_calendars.scss new file mode 100755 index 0000000..1e9fd15 --- /dev/null +++ b/user/themes/test/scss/spectre/_calendars.scss @@ -0,0 +1,222 @@ +// Calendars +.calendar { + border: $border-width solid $border-color; + border-radius: $border-radius; + display: block; + min-width: 280px; + + .calendar-nav { + align-items: center; + background: $bg-color; + border-top-left-radius: $border-radius; + border-top-right-radius: $border-radius; + display: flex; + font-size: $font-size-lg; + padding: $layout-spacing; + } + + .calendar-header, + .calendar-body { + display: flex; + flex-wrap: wrap; + justify-content: center; + padding: $layout-spacing 0; + + .calendar-date { + flex: 0 0 14.28%; // 7 calendar-items each row + max-width: 14.28%; + } + } + + .calendar-header { + background: $bg-color; + border-bottom: $border-width solid $border-color; + color: $gray-color; + font-size: $font-size-sm; + text-align: center; + } + + .calendar-body { + color: $gray-color-dark; + } + + .calendar-date { + border: 0; + padding: $unit-1; + + .date-item { + appearance: none; + background: transparent; + border: $border-width solid transparent; + border-radius: 50%; + color: $gray-color-dark; + cursor: pointer; + font-size: $font-size-sm; + height: $unit-7; + line-height: $unit-5; + outline: none; + padding: $unit-h; + position: relative; + text-align: center; + text-decoration: none; + transition: background .2s, border .2s, box-shadow .2s, color .2s; + vertical-align: middle; + white-space: nowrap; + width: $unit-7; + + &.date-today { + border-color: $secondary-color-dark; + color: $primary-color; + } + + &:focus { + @include control-shadow(); + } + + &:focus, + &:hover { + background: $secondary-color-light; + border-color: $secondary-color-dark; + color: $primary-color; + text-decoration: none; + } + &:active, + &.active { + background: $primary-color-dark; + border-color: darken($primary-color-dark, 5%); + color: $light-color; + } + + // Calendar badge support + &.badge { + &::after { + position: absolute; + top: 3px; + right: 3px; + transform: translate(50%, -50%); + } + } + } + + .date-item, + .calendar-event { + &:disabled, + &.disabled { + cursor: default; + opacity: .25; + pointer-events: none; + } + } + + &.prev-month, + &.next-month { + .date-item, + .calendar-event { + opacity: .25; + } + } + } + + .calendar-range { + position: relative; + + &::before { + background: $secondary-color; + content: ""; + height: $unit-7; + left: 0; + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); + } + &.range-start { + &::before { + left: 50%; + } + } + &.range-end { + &::before { + right: 50%; + } + } + + &.range-start, + &.range-end { + .date-item { + background: $primary-color-dark; + border-color: darken($primary-color-dark, 5%); + color: $light-color; + } + } + + .date-item { + color: $primary-color; + } + } + + // Calendars size + &.calendar-lg { + .calendar-body { + padding: 0; + + .calendar-date { + border-bottom: $border-width solid $border-color; + border-right: $border-width solid $border-color; + display: flex; + flex-direction: column; + height: 5.5rem; + padding: 0; + + &:nth-child(7n) { + border-right: 0; + } + &:nth-last-child(-n+7) { + border-bottom: 0; + } + } + } + + .date-item { + align-self: flex-end; + height: $unit-7; + margin-right: $layout-spacing-sm; + margin-top: $layout-spacing-sm; + } + + .calendar-range { + &::before { + top: 19px; + } + &.range-start { + &::before { + left: auto; + width: 19px; + } + } + &.range-end { + &::before { + right: 19px; + } + } + } + + .calendar-events { + flex-grow: 1; + line-height: 1; + overflow-y: auto; + padding: $layout-spacing-sm; + } + + .calendar-event { + border-radius: $border-radius; + font-size: $font-size-sm; + display: block; + margin: $unit-h auto; + overflow: hidden; + padding: 3px 4px; + text-overflow: ellipsis; + white-space: nowrap; + } + } +} diff --git a/user/themes/test/scss/spectre/_cards.scss b/user/themes/test/scss/spectre/_cards.scss new file mode 100755 index 0000000..6b712e1 --- /dev/null +++ b/user/themes/test/scss/spectre/_cards.scss @@ -0,0 +1,43 @@ +// Cards +.card { + background: $bg-color-light; + border: $border-width solid $border-color; + border-radius: $border-radius; + display: flex; + flex-direction: column; + + .card-header, + .card-body, + .card-footer { + padding: $layout-spacing-lg; + padding-bottom: 0; + + &:last-child { + padding-bottom: $layout-spacing-lg; + } + } + + .card-body { + flex: 1 1 auto; + } + + .card-image { + padding-top: $layout-spacing-lg; + + &:first-child { + padding-top: 0; + + img { + border-top-left-radius: $border-radius; + border-top-right-radius: $border-radius; + } + } + + &:last-child { + img { + border-bottom-left-radius: $border-radius; + border-bottom-right-radius: $border-radius; + } + } + } +} diff --git a/user/themes/test/scss/spectre/_carousels.scss b/user/themes/test/scss/spectre/_carousels.scss new file mode 100755 index 0000000..66dc51b --- /dev/null +++ b/user/themes/test/scss/spectre/_carousels.scss @@ -0,0 +1,136 @@ +// Carousels +// The number of carousel images +$carousel-number: 8; + +%carousel-image-checked { + animation: carousel-slidein .75s ease-in-out 1; + opacity: 1; + z-index: $zindex-1; +} + +%carousel-nav-checked { + color: $gray-color-light; +} + +.carousel { + background: $bg-color; + display: block; + overflow: hidden; + position: relative; + width: 100%; + -webkit-overflow-scrolling: touch; + z-index: $zindex-0; + + .carousel-container { + height: 100%; + left: 0; + position: relative; + &::before { + content: ""; + display: block; + padding-bottom: 56.25%; + } + + .carousel-item { + animation: carousel-slideout 1s ease-in-out 1; + height: 100%; + left: 0; + margin: 0; + opacity: 0; + position: absolute; + top: 0; + width: 100%; + + &:hover { + .item-prev, + .item-next { + opacity: 1; + } + } + } + + .item-prev, + .item-next { + background: rgba($gray-color-light, .25); + border-color: rgba($gray-color-light, .5); + color: $gray-color-light; + opacity: 0; + position: absolute; + top: 50%; + transition: all .4s; + transform: translateY(-50%); + z-index: $zindex-1; + } + .item-prev { + left: 1rem; + } + .item-next { + right: 1rem; + } + } + + .carousel-locator { + @for $i from 1 through ($carousel-number) { + &:nth-of-type(#{$i}):checked ~ .carousel-container .carousel-item:nth-of-type(#{$i}) { + @extend %carousel-image-checked; + } + } + + @for $i from 1 through ($carousel-number) { + &:nth-of-type(#{$i}):checked ~ .carousel-nav .nav-item:nth-of-type(#{$i}) { + @extend %carousel-nav-checked; + } + } + } + + .carousel-nav { + bottom: $layout-spacing; + display: flex; + justify-content: center; + left: 50%; + position: absolute; + transform: translateX(-50%); + width: 10rem; + z-index: $zindex-1; + + .nav-item { + color: rgba($gray-color-light, .5); + display: block; + flex: 1 0 auto; + height: $unit-8; + margin: $unit-1; + max-width: 2.5rem; + position: relative; + + &::before { + background: currentColor; + content: ""; + display: block; + height: $unit-h; + position: absolute; + top: .5rem; + width: 100%; + } + } + } +} + +@keyframes carousel-slidein { + 0% { + transform: translateX(100%); + } + 100% { + transform: translateX(0); + } +} + +@keyframes carousel-slideout { + 0% { + opacity: 1; + transform: translateX(0); + } + 100% { + opacity: 1; + transform: translateX(-50%); + } +} diff --git a/user/themes/test/scss/spectre/_chips.scss b/user/themes/test/scss/spectre/_chips.scss new file mode 100755 index 0000000..6729c56 --- /dev/null +++ b/user/themes/test/scss/spectre/_chips.scss @@ -0,0 +1,33 @@ +// Chips +.chip { + align-items: center; + background: $bg-color-dark; + border-radius: 5rem; + display: inline-flex; + font-size: 90%; + height: $unit-6; + line-height: $unit-4; + margin: $unit-h; + max-width: $control-width-sm; + overflow: hidden; + padding: $unit-1 $unit-2; + text-decoration: none; + text-overflow: ellipsis; + vertical-align: middle; + white-space: nowrap; + + &.active { + background: $primary-color; + color: $light-color; + } + + .avatar { + margin-left: -$unit-2; + margin-right: $unit-1; + } + + .btn-clear { + border-radius: 50%; + transform: scale(.75); + } +} diff --git a/user/themes/test/scss/spectre/_codes.scss b/user/themes/test/scss/spectre/_codes.scss new file mode 100755 index 0000000..817452b --- /dev/null +++ b/user/themes/test/scss/spectre/_codes.scss @@ -0,0 +1,31 @@ +// Codes +code { + @include label-base(); + @include label-variant($code-color, lighten($code-color, 42.5%)); + font-size: 85%; +} + +.code { + border-radius: $border-radius; + color: $body-font-color; + position: relative; + + &::before { + color: $gray-color; + content: attr(data-lang); + font-size: $font-size-sm; + position: absolute; + right: $layout-spacing; + top: $unit-h; + } + + code { + background: $bg-color; + color: inherit; + display: block; + line-height: 1.5; + overflow-x: auto; + padding: 1rem; + width: 100%; + } +} diff --git a/user/themes/test/scss/spectre/_comparison-sliders.scss b/user/themes/test/scss/spectre/_comparison-sliders.scss new file mode 100755 index 0000000..72bb25f --- /dev/null +++ b/user/themes/test/scss/spectre/_comparison-sliders.scss @@ -0,0 +1,115 @@ +// Image comparison slider +// Credit: http://codepen.io/solipsistacp/pen/Gpmaq +.comparison-slider { + height: 50vh; + overflow: hidden; + position: relative; + width: 100%; + -webkit-overflow-scrolling: touch; + + .comparison-before, + .comparison-after { + height: 100%; + left: 0; + margin: 0; + overflow: hidden; + position: absolute; + top: 0; + + img { + height: 100%; + object-fit: cover; + object-position: left center; + position: absolute; + width: 100%; + } + } + + .comparison-before { + width: 100%; + z-index: 1; + + .comparison-label { + right: $unit-4; + } + } + + .comparison-after { + max-width: 100%; + min-width: 0; + z-index: 2; + + &::before { + background: transparent; + content: ""; + cursor: default; + height: 100%; + left: 0; + position: absolute; + right: $unit-4; + top: 0; + z-index: $zindex-0; + } + + &::after { + background: currentColor; + border-radius: 50%; + box-shadow: 0 -5px, 0 5px; + color: $light-color; + content: ""; + height: 3px; + position: absolute; + right: $unit-2; + top: 50%; + transform: translate(50%, -50%); + width: 3px; + } + + .comparison-label { + left: $unit-4; + } + } + + .comparison-resizer { + animation: first-run 1.5s 1 ease-in-out; + cursor: ew-resize; + height: $unit-4; + left: 0; + max-width: 100%; + min-width: $unit-4; + opacity: 0; + outline: none; + position: relative; + resize: horizontal; + top: 50%; + transform: translateY(-50%) scaleY(30); + width: 0; + } + + .comparison-label { + background: rgba($dark-color, .5); + bottom: $unit-4; + color: $light-color; + padding: $unit-1 $unit-2; + position: absolute; + user-select: none; + } +} + +@keyframes first-run { + 0% { + width: 0; + } + 25% { + width: $unit-12; + } + 50% { + width: $unit-4; + } + 75% { + width: $unit-6; + } + 100% { + width: 0; + } +} diff --git a/user/themes/test/scss/spectre/_dropdowns.scss b/user/themes/test/scss/spectre/_dropdowns.scss new file mode 100755 index 0000000..324440b --- /dev/null +++ b/user/themes/test/scss/spectre/_dropdowns.scss @@ -0,0 +1,36 @@ +// Dropdown +.dropdown { + display: inline-block; + position: relative; + + .menu { + animation: slide-down .15s ease 1; + display: none; + left: 0; + max-height: 50vh; + overflow-y: auto; + position: absolute; + top: 100%; + } + + &.dropdown-right { + .menu { + left: auto; + right: 0; + } + } + + &.active .menu, + .dropdown-toggle:focus + .menu, + .menu:hover { + display: block; + } + + // Fix dropdown-toggle border radius in button groups + .btn-group { + .dropdown-toggle:nth-last-child(2) { + border-bottom-right-radius: $border-radius; + border-top-right-radius: $border-radius; + } + } +} diff --git a/user/themes/test/scss/spectre/_empty.scss b/user/themes/test/scss/spectre/_empty.scss new file mode 100755 index 0000000..accba9c --- /dev/null +++ b/user/themes/test/scss/spectre/_empty.scss @@ -0,0 +1,21 @@ +// Empty states (or Blank slates) +.empty { + background: $bg-color; + border-radius: $border-radius; + color: $gray-color-dark; + text-align: center; + padding: $unit-16 $unit-8; + + .empty-icon { + margin-bottom: $layout-spacing-lg; + } + + .empty-title, + .empty-subtitle { + margin: $layout-spacing auto; + } + + .empty-action { + margin-top: $layout-spacing-lg; + } +} diff --git a/user/themes/test/scss/spectre/_filters.scss b/user/themes/test/scss/spectre/_filters.scss new file mode 100755 index 0000000..37ccc89 --- /dev/null +++ b/user/themes/test/scss/spectre/_filters.scss @@ -0,0 +1,37 @@ +// Filters +// The number of filter options +$filter-number: 8 !default; + +%filter-checked-nav { + background: $primary-color; + color: $light-color; +} + +%filter-checked-body { + display: none; +} + +.filter { + .filter-nav { + margin: $layout-spacing 0; + } + + .filter-body { + display: flex; + flex-wrap: wrap; + } + + .filter-tag { + @for $i from 0 through ($filter-number) { + &#tag-#{$i}:checked ~ .filter-nav .chip[for="tag-#{$i}"] { + @extend %filter-checked-nav; + } + } + + @for $i from 1 through ($filter-number) { + &#tag-#{$i}:checked ~ .filter-body .filter-item:not([data-tag~="tag-#{$i}"]) { + @extend %filter-checked-body; + } + } + } +} diff --git a/user/themes/test/scss/spectre/_forms.scss b/user/themes/test/scss/spectre/_forms.scss new file mode 100755 index 0000000..20a6b4f --- /dev/null +++ b/user/themes/test/scss/spectre/_forms.scss @@ -0,0 +1,555 @@ +// Forms +.form-group { + &:not(:last-child) { + margin-bottom: $layout-spacing; + } +} + +fieldset { + margin-bottom: $layout-spacing-lg; +} + +legend { + font-size: $font-size-lg; + font-weight: 500; + margin-bottom: $layout-spacing-lg; +} + +// Form element: Label +.form-label { + display: block; + line-height: $line-height; + padding: $control-padding-y + $border-width 0; + + &.label-sm { + font-size: $font-size-sm; + padding: $control-padding-y-sm + $border-width 0; + } + + &.label-lg { + font-size: $font-size-lg; + padding: $control-padding-y-lg + $border-width 0; + } +} + +// Form element: Input +.form-input { + appearance: none; + background: $bg-color-light; + background-image: none; + border: $border-width solid $border-color-dark; + border-radius: $border-radius; + color: $body-font-color; + display: block; + font-size: $font-size; + height: $control-size; + line-height: $line-height; + max-width: 100%; + outline: none; + padding: $control-padding-y $control-padding-x; + position: relative; + transition: background .2s, border .2s, box-shadow .2s, color .2s; + width: 100%; + &:focus { + @include control-shadow(); + border-color: $primary-color; + } + &::placeholder { + color: $gray-color; + } + + // Input sizes + &.input-sm { + font-size: $font-size-sm; + height: $control-size-sm; + padding: $control-padding-y-sm $control-padding-x-sm; + } + + &.input-lg { + font-size: $font-size-lg; + height: $control-size-lg; + padding: $control-padding-y-lg $control-padding-x-lg; + } + + &.input-inline { + display: inline-block; + vertical-align: middle; + width: auto; + } + + // Input types + &[type="file"] { + height: auto; + } +} + +// Form element: Textarea +textarea.form-input { + &, + &.input-lg, + &.input-sm { + height: auto; + } +} + +// Form element: Input hint +.form-input-hint { + color: $gray-color; + font-size: $font-size-sm; + margin-top: $unit-1; + + .has-success &, + .is-success + & { + color: $success-color; + } + + .has-error &, + .is-error + & { + color: $error-color; + } +} + +// Form element: Select +.form-select { + appearance: none; + border: $border-width solid $border-color-dark; + border-radius: $border-radius; + color: inherit; + font-size: $font-size; + height: $control-size; + line-height: $line-height; + outline: none; + padding: $control-padding-y $control-padding-x; + vertical-align: middle; + width: 100%; + background: $bg-color-light; + &:focus { + @include control-shadow(); + border-color: $primary-color; + } + &::-ms-expand { + display: none; + } + + // Select sizes + &.select-sm { + font-size: $font-size-sm; + height: $control-size-sm; + padding: $control-padding-y-sm ($control-icon-size + $control-padding-x-sm) $control-padding-y-sm $control-padding-x-sm; + } + + &.select-lg { + font-size: $font-size-lg; + height: $control-size-lg; + padding: $control-padding-y-lg ($control-icon-size + $control-padding-x-lg) $control-padding-y-lg $control-padding-x-lg; + } + + // Multiple select + &[size], + &[multiple] { + height: auto; + padding: $control-padding-y $control-padding-x; + + option { + padding: $unit-h $unit-1; + } + } + &:not([multiple]):not([size]) { + background: $bg-color-light url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center / .4rem .5rem; + padding-right: $control-icon-size + $control-padding-x; + } +} + +// Form Icons +.has-icon-left, +.has-icon-right { + position: relative; + + .form-icon { + height: $control-icon-size; + margin: 0 $control-padding-y; + position: absolute; + top: 50%; + transform: translateY(-50%); + width: $control-icon-size; + z-index: $zindex-0 + 1; + } +} + +.has-icon-left { + .form-icon { + left: $border-width; + } + + .form-input { + padding-left: $control-icon-size + $control-padding-y * 2; + } +} + +.has-icon-right { + .form-icon { + right: $border-width; + } + + .form-input { + padding-right: $control-icon-size + $control-padding-y * 2; + } +} + +// Form element: Checkbox and Radio +.form-checkbox, +.form-radio, +.form-switch { + display: block; + line-height: $line-height; + margin: ($control-size - $control-size-sm) / 2 0; + min-height: $control-size-sm; + padding: (($control-size-sm - $line-height) / 2) $control-padding-x (($control-size-sm - $line-height) / 2) ($control-icon-size + $control-padding-x); + position: relative; + + input { + clip: rect(0, 0, 0, 0); + height: 1px; + margin: -1px; + overflow: hidden; + position: absolute; + width: 1px; + &:focus + .form-icon { + @include control-shadow(); + border-color: $primary-color; + } + &:checked + .form-icon { + background: $primary-color; + border-color: $primary-color; + } + } + + .form-icon { + border: $border-width solid $border-color-dark; + cursor: pointer; + display: inline-block; + position: absolute; + transition: background .2s, border .2s, box-shadow .2s, color .2s; + } + + // Input checkbox, radio and switch sizes + &.input-sm { + font-size: $font-size-sm; + margin: 0; + } + + &.input-lg { + font-size: $font-size-lg; + margin: ($control-size-lg - $control-size-sm) / 2 0; + } +} + +.form-checkbox, +.form-radio { + .form-icon { + background: $bg-color-light; + height: $control-icon-size; + left: 0; + top: ($control-size-sm - $control-icon-size) / 2; + width: $control-icon-size; + } + + input { + &:active + .form-icon { + background: $bg-color-dark; + } + } +} +.form-checkbox { + .form-icon { + border-radius: $border-radius; + } + + input { + &:checked + .form-icon { + &::before { + background-clip: padding-box; + border: $border-width-lg solid $light-color; + border-left-width: 0; + border-top-width: 0; + content: ""; + height: 9px; + left: 50%; + margin-left: -3px; + margin-top: -6px; + position: absolute; + top: 50%; + transform: rotate(45deg); + width: 6px; + } + } + &:indeterminate + .form-icon { + background: $primary-color; + border-color: $primary-color; + &::before { + background: $bg-color-light; + content: ""; + height: 2px; + left: 50%; + margin-left: -5px; + margin-top: -1px; + position: absolute; + top: 50%; + width: 10px; + } + } + } +} +.form-radio { + .form-icon { + border-radius: 50%; + } + + input { + &:checked + .form-icon { + &::before { + background: $bg-color-light; + border-radius: 50%; + content: ""; + height: 6px; + left: 50%; + position: absolute; + top: 50%; + transform: translate(-50%, -50%); + width: 6px; + } + } + } +} + +// Form element: Switch +.form-switch { + padding-left: ($unit-8 + $control-padding-x); + + .form-icon { + background: $gray-color; + background-clip: padding-box; + border-radius: $unit-2 + $border-width; + height: $unit-4 + $border-width * 2; + left: 0; + top: ($control-size-sm - $unit-4) / 2 - $border-width; + width: $unit-8; + &::before { + background: $bg-color-light; + border-radius: 50%; + content: ""; + display: block; + height: $unit-4; + left: 0; + position: absolute; + top: 0; + transition: background .2s, border .2s, box-shadow .2s, color .2s, left .2s; + width: $unit-4; + } + } + + input { + &:checked + .form-icon { + &::before { + left: 14px; + } + } + &:active + .form-icon { + &::before { + background: $bg-color; + } + } + } +} + +// Form element: Input groups +.input-group { + display: flex; + + .input-group-addon { + background: $bg-color; + border: $border-width solid $border-color-dark; + border-radius: $border-radius; + line-height: $line-height; + padding: $control-padding-y $control-padding-x; + white-space: nowrap; + + &.addon-sm { + font-size: $font-size-sm; + padding: $control-padding-y-sm $control-padding-x-sm; + } + + &.addon-lg { + font-size: $font-size-lg; + padding: $control-padding-y-lg $control-padding-x-lg; + } + } + + .form-input, + .form-select { + flex: 1 1 auto; + width: 1%; + } + + .input-group-btn { + z-index: $zindex-0; + } + + .form-input, + .form-select, + .input-group-addon, + .input-group-btn { + &:first-child:not(:last-child) { + border-bottom-right-radius: 0; + border-top-right-radius: 0; + } + &:not(:first-child):not(:last-child) { + border-radius: 0; + margin-left: -$border-width; + } + &:last-child:not(:first-child) { + border-bottom-left-radius: 0; + border-top-left-radius: 0; + margin-left: -$border-width; + } + &:focus { + z-index: $zindex-0 + 1; + } + } + + .form-select { + width: auto; + } + + &.input-inline { + display: inline-flex; + } +} + +// Form validation states +.form-input, +.form-select { + .has-success &, + &.is-success { + background: lighten($success-color, 53%); + border-color: $success-color; + &:focus { + @include control-shadow($success-color); + } + } + + .has-error &, + &.is-error { + background: lighten($error-color, 53%); + border-color: $error-color; + &:focus { + @include control-shadow($error-color); + } + } +} + +.form-checkbox, +.form-radio, +.form-switch { + .has-error &, + &.is-error { + .form-icon { + border-color: $error-color; + } + + input { + &:checked + .form-icon { + background: $error-color; + border-color: $error-color; + } + + &:focus + .form-icon { + @include control-shadow($error-color); + border-color: $error-color; + } + } + } +} + +.form-checkbox { + .has-error &, + &.is-error { + input { + &:indeterminate + .form-icon { + background: $error-color; + border-color: $error-color; + } + } + } +} + +// validation based on :placeholder-shown (Edge doesn't support it yet) +.form-input { + &:not(:placeholder-shown) { + &:invalid { + border-color: $error-color; + &:focus { + @include control-shadow($error-color); + background: lighten($error-color, 53%); + } + + & + .form-input-hint { + color: $error-color; + } + } + } +} + +// Form disabled and readonly +.form-input, +.form-select { + &:disabled, + &.disabled { + background-color: $bg-color-dark; + cursor: not-allowed; + opacity: .5; + } +} + +.form-input { + &[readonly] { + background-color: $bg-color; + } +} + +input { + &:disabled, + &.disabled { + & + .form-icon { + background: $bg-color-dark; + cursor: not-allowed; + opacity: .5; + } + } +} + +.form-switch { + input { + &:disabled, + &.disabled { + & + .form-icon::before { + background: $bg-color-light; + } + } + } +} + +// Form horizontal +.form-horizontal { + padding: $layout-spacing 0; + + .form-group { + display: flex; + flex-wrap: wrap; + } +} + +// Form inline +.form-inline { + display: inline-block; +} diff --git a/user/themes/test/scss/spectre/_hero.scss b/user/themes/test/scss/spectre/_hero.scss new file mode 100755 index 0000000..0044461 --- /dev/null +++ b/user/themes/test/scss/spectre/_hero.scss @@ -0,0 +1,22 @@ +// Hero +.hero { + display: flex; + flex-direction: column; + justify-content: space-between; + padding-bottom: 4rem; + padding-top: 4rem; + + &.hero-sm { + padding-bottom: 2rem; + padding-top: 2rem; + } + + &.hero-lg { + padding-bottom: 8rem; + padding-top: 8rem; + } + + .hero-body { + padding: $layout-spacing; + } +} \ No newline at end of file diff --git a/user/themes/test/scss/spectre/_icons.scss b/user/themes/test/scss/spectre/_icons.scss new file mode 100755 index 0000000..4f3c5ce --- /dev/null +++ b/user/themes/test/scss/spectre/_icons.scss @@ -0,0 +1,5 @@ +// CSS Icons +@import "icons/icons-core"; +@import "icons/icons-navigation"; +@import "icons/icons-action"; +@import "icons/icons-object"; \ No newline at end of file diff --git a/user/themes/test/scss/spectre/_labels.scss b/user/themes/test/scss/spectre/_labels.scss new file mode 100755 index 0000000..ca693cd --- /dev/null +++ b/user/themes/test/scss/spectre/_labels.scss @@ -0,0 +1,34 @@ +// Labels +.label { + @include label-base(); + @include label-variant(lighten($body-font-color, 5%), $bg-color-dark); + display: inline-block; + + // Label rounded + &.label-rounded { + border-radius: 5rem; + padding-left: .4rem; + padding-right: .4rem; + } + + // Label colors + &.label-primary { + @include label-variant($light-color, $primary-color); + } + + &.label-secondary { + @include label-variant($primary-color, $secondary-color); + } + + &.label-success { + @include label-variant($light-color, $success-color); + } + + &.label-warning { + @include label-variant($light-color, $warning-color); + } + + &.label-error { + @include label-variant($light-color, $error-color); + } +} diff --git a/user/themes/test/scss/spectre/_layout.scss b/user/themes/test/scss/spectre/_layout.scss new file mode 100755 index 0000000..1f6b77c --- /dev/null +++ b/user/themes/test/scss/spectre/_layout.scss @@ -0,0 +1,444 @@ +// Layout +.container { + margin-left: auto; + margin-right: auto; + padding-left: $layout-spacing; + padding-right: $layout-spacing; + width: 100%; + + $grid-spacing: ($layout-spacing / ($layout-spacing * 0 + 1)) * $html-font-size; + + &.grid-xl { + max-width: $grid-spacing * 2 + $size-xl; + } + + &.grid-lg { + max-width: $grid-spacing * 2 + $size-lg; + } + + &.grid-md { + max-width: $grid-spacing * 2 + $size-md; + } + + &.grid-sm { + max-width: $grid-spacing * 2 + $size-sm; + } + + &.grid-xs { + max-width: $grid-spacing * 2 + $size-xs; + } +} + +// Responsive breakpoint system +.show-xs, +.show-sm, +.show-md, +.show-lg, +.show-xl { + display: none !important; +} + +// Responsive grid system +.columns { + display: flex; + flex-wrap: wrap; + margin-left: -$layout-spacing; + margin-right: -$layout-spacing; + + &.col-gapless { + margin-left: 0; + margin-right: 0; + + & > .column { + padding-left: 0; + padding-right: 0; + } + } + &.col-oneline { + flex-wrap: nowrap; + overflow-x: auto; + } +} +.column { + flex: 1; + max-width: 100%; + padding-left: $layout-spacing; + padding-right: $layout-spacing; + + &.col-12, + &.col-11, + &.col-10, + &.col-9, + &.col-8, + &.col-7, + &.col-6, + &.col-5, + &.col-4, + &.col-3, + &.col-2, + &.col-1, + &.col-auto { + flex: none; + } +} +.col-12 { + width: 100%; +} +.col-11 { + width: 91.66666667%; +} +.col-10 { + width: 83.33333333%; +} +.col-9 { + width: 75%; +} +.col-8 { + width: 66.66666667%; +} +.col-7 { + width: 58.33333333%; +} +.col-6 { + width: 50%; +} +.col-5 { + width: 41.66666667%; +} +.col-4 { + width: 33.33333333%; +} +.col-3 { + width: 25%; +} +.col-2 { + width: 16.66666667%; +} +.col-1 { + width: 8.33333333%; +} +.col-auto { + flex: 0 0 auto; + max-width: none; + width: auto; +} +.col-mx-auto { + margin-left: auto; + margin-right: auto; +} +.col-ml-auto { + margin-left: auto; +} +.col-mr-auto { + margin-right: auto; +} +@media (max-width: $size-xl) { + .col-xl-12, + .col-xl-11, + .col-xl-10, + .col-xl-9, + .col-xl-8, + .col-xl-7, + .col-xl-6, + .col-xl-5, + .col-xl-4, + .col-xl-3, + .col-xl-2, + .col-xl-1, + .col-xl-auto { + flex: none; + } + .col-xl-12 { + width: 100%; + } + .col-xl-11 { + width: 91.66666667%; + } + .col-xl-10 { + width: 83.33333333%; + } + .col-xl-9 { + width: 75%; + } + .col-xl-8 { + width: 66.66666667%; + } + .col-xl-7 { + width: 58.33333333%; + } + .col-xl-6 { + width: 50%; + } + .col-xl-5 { + width: 41.66666667%; + } + .col-xl-4 { + width: 33.33333333%; + } + .col-xl-3 { + width: 25%; + } + .col-xl-2 { + width: 16.66666667%; + } + .col-xl-1 { + width: 8.33333333%; + } + .col-xl-auto { + width: auto; + } + .hide-xl { + display: none !important; + } + .show-xl { + display: block !important; + } +} +@media (max-width: $size-lg) { + .col-lg-12, + .col-lg-11, + .col-lg-10, + .col-lg-9, + .col-lg-8, + .col-lg-7, + .col-lg-6, + .col-lg-5, + .col-lg-4, + .col-lg-3, + .col-lg-2, + .col-lg-1, + .col-lg-auto { + flex: none; + } + .col-lg-12 { + width: 100%; + } + .col-lg-11 { + width: 91.66666667%; + } + .col-lg-10 { + width: 83.33333333%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-8 { + width: 66.66666667%; + } + .col-lg-7 { + width: 58.33333333%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-5 { + width: 41.66666667%; + } + .col-lg-4 { + width: 33.33333333%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-2 { + width: 16.66666667%; + } + .col-lg-1 { + width: 8.33333333%; + } + .col-lg-auto { + width: auto; + } + .hide-lg { + display: none !important; + } + .show-lg { + display: block !important; + } +} +@media (max-width: $size-md) { + .col-md-12, + .col-md-11, + .col-md-10, + .col-md-9, + .col-md-8, + .col-md-7, + .col-md-6, + .col-md-5, + .col-md-4, + .col-md-3, + .col-md-2, + .col-md-1, + .col-md-auto { + flex: none; + } + .col-md-12 { + width: 100%; + } + .col-md-11 { + width: 91.66666667%; + } + .col-md-10 { + width: 83.33333333%; + } + .col-md-9 { + width: 75%; + } + .col-md-8 { + width: 66.66666667%; + } + .col-md-7 { + width: 58.33333333%; + } + .col-md-6 { + width: 50%; + } + .col-md-5 { + width: 41.66666667%; + } + .col-md-4 { + width: 33.33333333%; + } + .col-md-3 { + width: 25%; + } + .col-md-2 { + width: 16.66666667%; + } + .col-md-1 { + width: 8.33333333%; + } + .col-md-auto { + width: auto; + } + .hide-md { + display: none !important; + } + .show-md { + display: block !important; + } +} +@media (max-width: $size-sm) { + .col-sm-12, + .col-sm-11, + .col-sm-10, + .col-sm-9, + .col-sm-8, + .col-sm-7, + .col-sm-6, + .col-sm-5, + .col-sm-4, + .col-sm-3, + .col-sm-2, + .col-sm-1, + .col-sm-auto { + flex: none; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } + .col-sm-auto { + width: auto; + } + .hide-sm { + display: none !important; + } + .show-sm { + display: block !important; + } +} +@media (max-width: $size-xs) { + .col-xs-12, + .col-xs-11, + .col-xs-10, + .col-xs-9, + .col-xs-8, + .col-xs-7, + .col-xs-6, + .col-xs-5, + .col-xs-4, + .col-xs-3, + .col-xs-2, + .col-xs-1, + .col-xs-auto { + flex: none; + } + .col-xs-12 { + width: 100%; + } + .col-xs-11 { + width: 91.66666667%; + } + .col-xs-10 { + width: 83.33333333%; + } + .col-xs-9 { + width: 75%; + } + .col-xs-8 { + width: 66.66666667%; + } + .col-xs-7 { + width: 58.33333333%; + } + .col-xs-6 { + width: 50%; + } + .col-xs-5 { + width: 41.66666667%; + } + .col-xs-4 { + width: 33.33333333%; + } + .col-xs-3 { + width: 25%; + } + .col-xs-2 { + width: 16.66666667%; + } + .col-xs-1 { + width: 8.33333333%; + } + .col-xs-auto { + width: auto; + } + .hide-xs { + display: none !important; + } + .show-xs { + display: block !important; + } +} diff --git a/user/themes/test/scss/spectre/_media.scss b/user/themes/test/scss/spectre/_media.scss new file mode 100755 index 0000000..4029e4c --- /dev/null +++ b/user/themes/test/scss/spectre/_media.scss @@ -0,0 +1,75 @@ +// Media +// Image responsive +.img-responsive { + display: block; + height: auto; + max-width: 100%; +} + +// object-fit support is coming to Microsoft Edge +// https://developer.microsoft.com/en-us/microsoft-edge/platform/status/objectfitandobjectposition/ +.img-fit-cover { + object-fit: cover; +} + +.img-fit-contain { + object-fit: contain; +} + +// Video responsive +.video-responsive { + display: block; + overflow: hidden; + padding: 0; + position: relative; + width: 100%; + &::before { + content: ""; + display: block; + padding-bottom: 56.25%; // Default ratio 16:9, you can calculate this value by dividing 9 by 16 + } + + iframe, + object, + embed { + border: 0; + bottom: 0; + height: 100%; + left: 0; + position: absolute; + right: 0; + top: 0; + width: 100%; + } +} + +video.video-responsive { + height: auto; + max-width: 100%; + + &::before { + content: none; + } +} + +.video-responsive-4-3 { + &::before { + padding-bottom: 75%; // Ratio 4:3 + } +} + +.video-responsive-1-1 { + &::before { + padding-bottom: 100%; // Ratio 1:1 + } +} + +// Figure +.figure { + margin: 0 0 $layout-spacing 0; + + .figure-caption { + color: $gray-color-dark; + margin-top: $layout-spacing; + } +} diff --git a/user/themes/test/scss/spectre/_menus.scss b/user/themes/test/scss/spectre/_menus.scss new file mode 100755 index 0000000..411cada --- /dev/null +++ b/user/themes/test/scss/spectre/_menus.scss @@ -0,0 +1,66 @@ +// Menus +.menu { + @include shadow-variant(.05rem); + background: $bg-color-light; + border-radius: $border-radius; + list-style: none; + margin: 0; + min-width: $control-width-xs; + padding: $unit-2; + transform: translateY($layout-spacing-sm); + z-index: $zindex-3; + + &.menu-nav { + background: transparent; + box-shadow: none; + } + + .menu-item { + margin-top: 0; + padding: 0 $unit-2; + position: relative; + text-decoration: none; + + & > a { + border-radius: $border-radius; + color: inherit; + display: block; + margin: 0 (-$unit-2); + padding: $unit-1 $unit-2; + text-decoration: none; + &:focus, + &:hover { + background: $secondary-color; + color: $primary-color; + } + &:active, + &.active { + background: $secondary-color; + color: $primary-color; + } + } + + .form-checkbox, + .form-radio, + .form-switch { + margin: $unit-h 0; + } + + & + .menu-item { + margin-top: $unit-1; + } + } + + .menu-badge { + align-items: center; + display: flex; + height: 100%; + position: absolute; + right: 0; + top: 0; + + .label { + margin-right: $unit-2; + } + } +} \ No newline at end of file diff --git a/user/themes/test/scss/spectre/_meters.scss b/user/themes/test/scss/spectre/_meters.scss new file mode 100755 index 0000000..9fd98b0 --- /dev/null +++ b/user/themes/test/scss/spectre/_meters.scss @@ -0,0 +1,57 @@ +// Meters +// Credit: https://css-tricks.com/html5-meter-element/ +.meter { + appearance: none; + background: $bg-color; + border: 0; + border-radius: $border-radius; + display: block; + width: 100%; + height: $unit-4; + + &::-webkit-meter-inner-element { + display: block; + } + + &::-webkit-meter-bar, + &::-webkit-meter-optimum-value, + &::-webkit-meter-suboptimum-value, + &::-webkit-meter-even-less-good-value { + border-radius: $border-radius; + } + + &::-webkit-meter-bar { + background: $bg-color; + } + + &::-webkit-meter-optimum-value { + background: $success-color; + } + + &::-webkit-meter-suboptimum-value { + background: $warning-color; + } + + &::-webkit-meter-even-less-good-value { + background: $error-color; + } + + &::-moz-meter-bar, + &:-moz-meter-optimum, + &:-moz-meter-sub-optimum, + &:-moz-meter-sub-sub-optimum { + border-radius: $border-radius; + } + + &:-moz-meter-optimum::-moz-meter-bar { + background: $success-color; + } + + &:-moz-meter-sub-optimum::-moz-meter-bar { + background: $warning-color; + } + + &:-moz-meter-sub-sub-optimum::-moz-meter-bar { + background: $error-color; + } +} diff --git a/user/themes/test/scss/spectre/_mixins.scss b/user/themes/test/scss/spectre/_mixins.scss new file mode 100755 index 0000000..d3a28d5 --- /dev/null +++ b/user/themes/test/scss/spectre/_mixins.scss @@ -0,0 +1,10 @@ +// Mixins +@import "mixins/avatar"; +@import "mixins/button"; +@import "mixins/clearfix"; +@import "mixins/color"; +@import "mixins/label"; +@import "mixins/position"; +@import "mixins/shadow"; +@import "mixins/text"; +@import "mixins/toast"; \ No newline at end of file diff --git a/user/themes/test/scss/spectre/_modals.scss b/user/themes/test/scss/spectre/_modals.scss new file mode 100755 index 0000000..a7b3f10 --- /dev/null +++ b/user/themes/test/scss/spectre/_modals.scss @@ -0,0 +1,87 @@ +// Modals +.modal { + align-items: center; + bottom: 0; + display: none; + justify-content: center; + left: 0; + opacity: 0; + overflow: hidden; + padding: $layout-spacing; + position: fixed; + right: 0; + top: 0; + + &:target, + &.active { + display: flex; + opacity: 1; + z-index: $zindex-4; + + .modal-overlay { + background: rgba($bg-color, .75); + bottom: 0; + cursor: default; + display: block; + left: 0; + position: absolute; + right: 0; + top: 0; + } + + .modal-container { + animation: slide-down .2s ease 1; + z-index: $zindex-0; + } + } + + &.modal-sm { + .modal-container { + max-width: $control-width-sm; + padding: 0 $unit-2; + } + } + + &.modal-lg { + .modal-overlay { + background: $bg-color-light; + } + + .modal-container { + box-shadow: none; + max-width: $control-width-lg; + } + } +} + +.modal-container { + @include shadow-variant(.2rem); + background: $bg-color-light; + border-radius: $border-radius; + display: flex; + flex-direction: column; + max-height: 75vh; + max-width: $control-width-md; + padding: 0 $unit-4; + width: 100%; + + &.modal-fullheight { + max-height: 100vh; + } + + .modal-header { + color: $dark-color; + padding: $unit-4; + } + + .modal-body { + overflow-y: auto; + padding: $unit-4; + position: relative; + } + + .modal-footer { + padding: $unit-4; + text-align: right; + } +} diff --git a/user/themes/test/scss/spectre/_navbar.scss b/user/themes/test/scss/spectre/_navbar.scss new file mode 100755 index 0000000..1164296 --- /dev/null +++ b/user/themes/test/scss/spectre/_navbar.scss @@ -0,0 +1,28 @@ +// Navbar +.navbar { + align-items: stretch; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + + .navbar-section { + align-items: center; + display: flex; + flex: 1 0 0; + + &:not(:first-child):last-child { + justify-content: flex-end; + } + } + + .navbar-center { + align-items: center; + display: flex; + flex: 0 0 auto; + } + + .navbar-brand { + font-size: $font-size-lg; + text-decoration: none; + } +} diff --git a/user/themes/test/scss/spectre/_navs.scss b/user/themes/test/scss/spectre/_navs.scss new file mode 100755 index 0000000..4bedc27 --- /dev/null +++ b/user/themes/test/scss/spectre/_navs.scss @@ -0,0 +1,34 @@ +// Navs +.nav { + display: flex; + flex-direction: column; + list-style: none; + margin: $unit-1 0; + + .nav-item { + a { + color: $gray-color-dark; + padding: $unit-1 $unit-2; + text-decoration: none; + &:focus, + &:hover { + color: $primary-color; + } + } + &.active { + & > a { + color: darken($gray-color-dark, 10%); + font-weight: bold; + &:focus, + &:hover { + color: $primary-color; + } + } + } + } + + & .nav { + margin-bottom: $unit-2; + margin-left: $unit-4; + } +} diff --git a/user/themes/test/scss/spectre/_normalize.scss b/user/themes/test/scss/spectre/_normalize.scss new file mode 100755 index 0000000..a098a84 --- /dev/null +++ b/user/themes/test/scss/spectre/_normalize.scss @@ -0,0 +1,446 @@ +/* Manually forked from Normalize.css */ +/* normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */ + +/** + * 1. Change the default font family in all browsers (opinionated). + * 2. Correct the line height in all browsers. + * 3. Prevent adjustments of font size after orientation changes in + * IE on Windows Phone and in iOS. + */ + +/* Document + ========================================================================== */ + +html { + font-family: sans-serif; /* 1 */ + -ms-text-size-adjust: 100%; /* 3 */ + -webkit-text-size-adjust: 100%; /* 3 */ +} + +/* Sections + ========================================================================== */ + +/** + * Remove the margin in all browsers (opinionated). + */ + +body { + margin: 0; +} + +/** + * Add the correct display in IE 9-. + */ + +article, +aside, +footer, +header, +nav, +section { + display: block; +} + +/** + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/* Grouping content + ========================================================================== */ + +/** + * Add the correct display in IE 9-. + * 1. Add the correct display in IE. + */ + +figcaption, +figure, +main { /* 1 */ + display: block; +} + +/** + * Add the correct margin in IE 8 (removed). + */ + +/** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ + +hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. (removed) + * 2. Correct the odd `em` font sizing in all browsers. + */ + +/* Text-level semantics + ========================================================================== */ + +/** + * 1. Remove the gray background on active links in IE 10. + * 2. Remove gaps in links underline in iOS 8+ and Safari 8+. + */ + +a { + background-color: transparent; /* 1 */ + -webkit-text-decoration-skip: objects; /* 2 */ +} + +/** + * Remove the outline on focused links when they are also active or hovered + * in all browsers (opinionated). + */ + +a:active, +a:hover { + outline-width: 0; +} + +/** + * Modify default styling of address. + */ + +address { + font-style: normal; +} + +/** + * 1. Remove the bottom border in Firefox 39-. + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. (removed) + */ + +/** + * Prevent the duplicate application of `bolder` by the next rule in Safari 6. + */ + +b, +strong { + font-weight: inherit; +} + +/** + * Add the correct font weight in Chrome, Edge, and Safari. + */ + +b, +strong { + font-weight: bolder; +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +code, +kbd, +pre, +samp { + font-family: $mono-font-family; /* 1 (changed) */ + font-size: 1em; /* 2 */ +} + +/** + * Add the correct font style in Android 4.3-. + */ + +dfn { + font-style: italic; +} + +/** + * Add the correct background and color in IE 9-. (Removed) + */ + +/** + * Add the correct font size in all browsers. + */ + +small { + font-size: 80%; + font-weight: 400; /* (added) */ +} + +/** + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Add the correct display in IE 9-. + */ + +audio, +video { + display: inline-block; +} + +/** + * Add the correct display in iOS 4-7. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Remove the border on images inside links in IE 10-. + */ + +img { + border-style: none; +} + +/** + * Hide the overflow in IE. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Forms + ========================================================================== */ + +/** + * 1. Change the font styles in all browsers (opinionated). + * 2. Remove the margin in Firefox and Safari. + */ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; /* 1 (changed) */ + font-size: inherit; /* 1 (changed) */ + line-height: inherit; /* 1 (changed) */ + margin: 0; /* 2 */ +} + +/** + * Show the overflow in IE. + * 1. Show the overflow in Edge. + */ + +button, +input { /* 1 */ + overflow: visible; +} + +/** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ + +button, +select { /* 1 */ + text-transform: none; +} + +/** + * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` + * controls in Android 4. + * 2. Correct the inability to style clickable types in iOS and Safari. + */ + +button, +html [type="button"], /* 1 */ +[type="reset"], +[type="submit"] { + -webkit-appearance: button; /* 2 */ +} + +/** + * Remove the inner border and padding in Firefox. + */ + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +/** + * Restore the focus styles unset by the previous rule (removed). + */ + + +/** + * Change the border, margin, and padding in all browsers (opinionated) (changed). + */ + +fieldset { + border: 0; + margin: 0; + padding: 0; +} + +/** + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. + */ + +legend { + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ +} + +/** + * 1. Add the correct display in IE 9-. + * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ + +progress { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Remove the default vertical scrollbar in IE. + */ + +textarea { + overflow: auto; +} + +/** + * 1. Add the correct box sizing in IE 10-. + * 2. Remove the padding in IE 10-. + */ + +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ + +[type="search"] { + -webkit-appearance: textfield; /* 1 */ + outline-offset: -2px; /* 2 */ +} + +/** + * Remove the inner padding and cancel buttons in Chrome and Safari on macOS. + */ + +[type="search"]::-webkit-search-cancel-button, +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ + +::-webkit-file-upload-button { + -webkit-appearance: button; /* 1 */ + font: inherit; /* 2 */ +} + +/* Interactive + ========================================================================== */ + +/* + * Add the correct display in IE 9-. + * 1. Add the correct display in Edge, IE, and Firefox. + */ + +details, /* 1 */ +menu { + display: block; +} + +/* + * Add the correct display in all browsers. + */ + +summary { + display: list-item; + outline: none; +} + +/* Scripting + ========================================================================== */ + +/** + * Add the correct display in IE 9-. + */ + +canvas { + display: inline-block; +} + +/** + * Add the correct display in IE. + */ + +template { + display: none; +} + +/* Hidden + ========================================================================== */ + +/** + * Add the correct display in IE 10-. + */ + +[hidden] { + display: none; +} diff --git a/user/themes/test/scss/spectre/_off-canvas.scss b/user/themes/test/scss/spectre/_off-canvas.scss new file mode 100755 index 0000000..f3b8b9f --- /dev/null +++ b/user/themes/test/scss/spectre/_off-canvas.scss @@ -0,0 +1,95 @@ +// Off canvas menus +$off-canvas-breakpoint: $size-lg !default; + +.off-canvas { + display: flex; + flex-flow: nowrap; + height: 100%; + position: relative; + width: 100%; + + .off-canvas-toggle { + display: block; + position: absolute; + top: $layout-spacing; + transition: none; + z-index: $zindex-0; + @if $rtl == true { + right: $layout-spacing; + } @else { + left: $layout-spacing; + } + } + + .off-canvas-sidebar { + background: $bg-color; + bottom: 0; + min-width: 10rem; + overflow-y: auto; + position: fixed; + top: 0; + transition: transform .25s; + z-index: $zindex-2; + @if $rtl == true { + right: 0; + transform: translateX(100%); + } @else { + left: 0; + transform: translateX(-100%); + } + } + + .off-canvas-content { + flex: 1 1 auto; + height: 100%; + padding: $layout-spacing $layout-spacing $layout-spacing 4rem; + } + + .off-canvas-overlay { + background: rgba($dark-color, .1); + border-color: transparent; + border-radius: 0; + bottom: 0; + display: none; + height: 100%; + left: 0; + position: fixed; + right: 0; + top: 0; + width: 100%; + } + + .off-canvas-sidebar { + &:target, + &.active { + transform: translateX(0); + } + + &:target ~ .off-canvas-overlay, + &.active ~ .off-canvas-overlay { + display: block; + z-index: $zindex-1; + } + } +} + +// Responsive layout +@media (min-width: $off-canvas-breakpoint) { + .off-canvas { + &.off-canvas-sidebar-show { + .off-canvas-toggle { + display: none; + } + + .off-canvas-sidebar { + flex: 0 0 auto; + position: relative; + transform: none; + } + + .off-canvas-overlay { + display: none !important; + } + } + } +} diff --git a/user/themes/test/scss/spectre/_pagination.scss b/user/themes/test/scss/spectre/_pagination.scss new file mode 100755 index 0000000..4c0e011 --- /dev/null +++ b/user/themes/test/scss/spectre/_pagination.scss @@ -0,0 +1,60 @@ +// Pagination +.pagination { + display: flex; + list-style: none; + margin: $unit-1 0; + padding: $unit-1 0; + + .page-item { + margin: $unit-1 $unit-o; + + span { + display: inline-block; + padding: $unit-1 $unit-1; + } + + a { + border-radius: $border-radius; + display: inline-block; + padding: $unit-1 $unit-2; + text-decoration: none; + &:focus, + &:hover { + color: $primary-color; + } + } + + &.disabled { + a { + cursor: default; + opacity: .5; + pointer-events: none; + } + } + + &.active { + a { + background: $primary-color; + color: $light-color; + } + } + + &.page-prev, + &.page-next { + flex: 1 0 50%; + } + + &.page-next { + text-align: right; + } + + .page-item-title { + margin: 0; + } + + .page-item-subtitle { + margin: 0; + opacity: .5; + } + } +} diff --git a/user/themes/test/scss/spectre/_panels.scss b/user/themes/test/scss/spectre/_panels.scss new file mode 100755 index 0000000..386f96e --- /dev/null +++ b/user/themes/test/scss/spectre/_panels.scss @@ -0,0 +1,23 @@ +// Panels +.panel { + border: $border-width solid $border-color; + border-radius: $border-radius; + display: flex; + flex-direction: column; + + .panel-header, + .panel-footer { + flex: 0 0 auto; + padding: $layout-spacing-lg; + } + + .panel-nav { + flex: 0 0 auto; + } + + .panel-body { + flex: 1 1 auto; + overflow-y: auto; + padding: 0 $layout-spacing-lg; + } +} diff --git a/user/themes/test/scss/spectre/_parallax.scss b/user/themes/test/scss/spectre/_parallax.scss new file mode 100755 index 0000000..ea244e5 --- /dev/null +++ b/user/themes/test/scss/spectre/_parallax.scss @@ -0,0 +1,135 @@ +// Parallax +$parallax-deg: 3deg !default; +$parallax-offset: 4.5px !default; +$parallax-offset-z: 50px !default; +$parallax-perspective: 1000px !default; +$parallax-scale: .95 !default; +$parallax-fade-color: rgba(255, 255, 255, .35) !default; + +// Mixin: Parallax direction +@mixin parallax-dir() { + height: 50%; + outline: none; + position: absolute; + width: 50%; + z-index: $zindex-1; +} + +.parallax { + display: block; + height: auto; + position: relative; + width: auto; + + .parallax-content { + @include shadow-variant(1rem); + height: auto; + transform: perspective($parallax-perspective); + transform-style: preserve-3d; + transition: all .4s ease; + width: 100%; + + &::before { + content: ""; + display: block; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; + } + } + + .parallax-front { + align-items: center; + color: $light-color; + display: flex; + height: 100%; + justify-content: center; + left: 0; + position: absolute; + text-align: center; + text-shadow: 0 0 20px rgba($dark-color, .75); + top: 0; + transform: translateZ($parallax-offset-z) scale($parallax-scale); + transition: transform .4s; + width: 100%; + z-index: $zindex-0; + } + + .parallax-top-left { + @include parallax-dir(); + left: 0; + top: 0; + + &:focus ~ .parallax-content, + &:hover ~ .parallax-content { + transform: perspective($parallax-perspective) rotateX($parallax-deg) rotateY(-$parallax-deg); + + &::before { + background: linear-gradient(135deg, $parallax-fade-color 0%, transparent 50%); + } + + .parallax-front { + transform: translate3d($parallax-offset, $parallax-offset, $parallax-offset-z) scale($parallax-scale); + } + } + } + + .parallax-top-right { + @include parallax-dir(); + right: 0; + top: 0; + + &:focus ~ .parallax-content, + &:hover ~ .parallax-content { + transform: perspective($parallax-perspective) rotateX($parallax-deg) rotateY($parallax-deg); + + &::before { + background: linear-gradient(-135deg, $parallax-fade-color 0%, transparent 50%); + } + + .parallax-front { + transform: translate3d(-$parallax-offset, $parallax-offset, $parallax-offset-z) scale($parallax-scale); + } + } + } + + .parallax-bottom-left { + @include parallax-dir(); + bottom: 0; + left: 0; + + &:focus ~ .parallax-content, + &:hover ~ .parallax-content { + transform: perspective($parallax-perspective) rotateX(-$parallax-deg) rotateY(-$parallax-deg); + + &::before { + background: linear-gradient(45deg, $parallax-fade-color 0%, transparent 50%); + } + + .parallax-front { + transform: translate3d($parallax-offset, -$parallax-offset, $parallax-offset-z) scale($parallax-scale); + } + } + } + + .parallax-bottom-right { + @include parallax-dir(); + bottom: 0; + right: 0; + + &:focus ~ .parallax-content, + &:hover ~ .parallax-content { + transform: perspective($parallax-perspective) rotateX(-$parallax-deg) rotateY($parallax-deg); + + &::before { + background: linear-gradient(-45deg, $parallax-fade-color 0%, transparent 50%); + } + + .parallax-front { + transform: translate3d(-$parallax-offset, -$parallax-offset, $parallax-offset-z) scale($parallax-scale); + } + } + } +} diff --git a/user/themes/test/scss/spectre/_popovers.scss b/user/themes/test/scss/spectre/_popovers.scss new file mode 100755 index 0000000..35b6bcd --- /dev/null +++ b/user/themes/test/scss/spectre/_popovers.scss @@ -0,0 +1,65 @@ +// Popovers +.popover { + display: inline-block; + position: relative; + + .popover-container { + left: 50%; + opacity: 0; + padding: $layout-spacing; + position: absolute; + top: 0; + transform: translate(-50%, -50%) scale(0); + transition: transform .2s; + width: $control-width-sm; + z-index: $zindex-3; + } + + *:focus + .popover-container, + &:hover .popover-container { + display: block; + opacity: 1; + transform: translate(-50%, -100%) scale(1); + } + + &.popover-right { + .popover-container { + left: 100%; + top: 50%; + } + + *:focus + .popover-container, + &:hover .popover-container { + transform: translate(0, -50%) scale(1); + } + } + + &.popover-bottom { + .popover-container { + left: 50%; + top: 100%; + } + + *:focus + .popover-container, + &:hover .popover-container { + transform: translate(-50%, 0) scale(1); + } + } + + &.popover-left { + .popover-container { + left: 0; + top: 50%; + } + + *:focus + .popover-container, + &:hover .popover-container { + transform: translate(-100%, -50%) scale(1); + } + } + + .card { + @include shadow-variant(.2rem); + border: 0; + } +} diff --git a/user/themes/test/scss/spectre/_progress.scss b/user/themes/test/scss/spectre/_progress.scss new file mode 100755 index 0000000..f173772 --- /dev/null +++ b/user/themes/test/scss/spectre/_progress.scss @@ -0,0 +1,45 @@ +// Progress +// Credit: https://css-tricks.com/html5-progress-element/ +.progress { + appearance: none; + background: $bg-color-dark; + border: 0; + border-radius: $border-radius; + color: $primary-color; + height: $unit-1; + position: relative; + width: 100%; + + &::-webkit-progress-bar { + background: transparent; + border-radius: $border-radius; + } + + &::-webkit-progress-value { + background: $primary-color; + border-radius: $border-radius; + } + + &::-moz-progress-bar { + background: $primary-color; + border-radius: $border-radius; + } + + &:indeterminate { + animation: progress-indeterminate 1.5s linear infinite; + background: $bg-color-dark linear-gradient(to right, $primary-color 30%, $bg-color-dark 30%) top left / 150% 150% no-repeat; + + &::-moz-progress-bar { + background: transparent; + } + } +} + +@keyframes progress-indeterminate { + 0% { + background-position: 200% 0; + } + 100% { + background-position: -200% 0; + } +} diff --git a/user/themes/test/scss/spectre/_sliders.scss b/user/themes/test/scss/spectre/_sliders.scss new file mode 100755 index 0000000..3ff38e8 --- /dev/null +++ b/user/themes/test/scss/spectre/_sliders.scss @@ -0,0 +1,99 @@ +// Sliders +// Credit: https://css-tricks.com/styling-cross-browser-compatible-range-inputs-css/ +.slider { + appearance: none; + background: transparent; + display: block; + width: 100%; + height: $unit-6; + + &:focus { + @include control-shadow(); + outline: none; + } + + &.tooltip:not([data-tooltip]) { + &::after { + content: attr(value); + } + } + + // Slider Thumb + &::-webkit-slider-thumb { + -webkit-appearance: none; + background: $primary-color; + border: 0; + border-radius: 50%; + height: $unit-3; + margin-top: -($unit-3 - $unit-h) / 2; + transition: transform .2s; + width: $unit-3; + } + &::-moz-range-thumb { + background: $primary-color; + border: 0; + border-radius: 50%; + height: $unit-3; + transition: transform .2s; + width: $unit-3; + } + &::-ms-thumb { + background: $primary-color; + border: 0; + border-radius: 50%; + height: $unit-3; + transition: transform .2s; + width: $unit-3; + } + + &:active { + &::-webkit-slider-thumb { + transform: scale(1.25); + } + &::-moz-range-thumb { + transform: scale(1.25); + } + &::-ms-thumb { + transform: scale(1.25); + } + } + + &:disabled, + &.disabled { + &::-webkit-slider-thumb { + background: $gray-color-light; + transform: scale(1); + } + &::-moz-range-thumb { + background: $gray-color-light; + transform: scale(1); + } + &::-ms-thumb { + background: $gray-color-light; + transform: scale(1); + } + } + + // Slider Track + &::-webkit-slider-runnable-track { + background: $bg-color-dark; + border-radius: $border-radius; + height: $unit-h; + width: 100%; + } + &::-moz-range-track { + background: $bg-color-dark; + border-radius: $border-radius; + height: $unit-h; + width: 100%; + } + &::-ms-track { + background: $bg-color-dark; + border-radius: $border-radius; + height: $unit-h; + width: 100%; + } + &::-ms-fill-lower { + background: $primary-color; + } +} diff --git a/user/themes/test/scss/spectre/_steps.scss b/user/themes/test/scss/spectre/_steps.scss new file mode 100755 index 0000000..f642ff8 --- /dev/null +++ b/user/themes/test/scss/spectre/_steps.scss @@ -0,0 +1,71 @@ +// Steps +.step { + display: flex; + flex-wrap: nowrap; + list-style: none; + margin: $unit-1 0; + width: 100%; + + .step-item { + flex: 1 1 0; + margin-top: 0; + min-height: 1rem; + text-align: center; + position: relative; + + &:not(:first-child)::before { + background: $primary-color; + content: ""; + height: 2px; + left: -50%; + position: absolute; + top: 9px; + width: 100%; + } + + a { + color: $primary-color; + display: inline-block; + padding: 20px 10px 0; + text-decoration: none; + + &::before { + background: $primary-color; + border: $border-width-lg solid $light-color; + border-radius: 50%; + content: ""; + display: block; + height: $unit-3; + left: 50%; + position: absolute; + top: $unit-1; + transform: translateX(-50%); + width: $unit-3; + z-index: $zindex-0; + } + } + + &.active { + a { + &::before { + background: $light-color; + border: $border-width-lg solid $primary-color; + } + } + + & ~ .step-item { + &::before { + background: $border-color; + } + + a { + color: $gray-color; + + &::before { + background: $border-color; + } + } + } + } + } +} diff --git a/user/themes/test/scss/spectre/_tables.scss b/user/themes/test/scss/spectre/_tables.scss new file mode 100755 index 0000000..656c03e --- /dev/null +++ b/user/themes/test/scss/spectre/_tables.scss @@ -0,0 +1,57 @@ +// Tables +.table { + border-collapse: collapse; + border-spacing: 0; + width: 100%; + @if $rtl == true { + text-align: right; + } @else { + text-align: left; + } + + &.table-striped { + tbody { + tr:nth-of-type(odd) { + background: $bg-color; + } + } + } + + &, + &.table-striped { + tbody { + tr { + &.active { + background: $bg-color-dark; + } + } + } + } + + &.table-hover { + tbody { + tr { + &:hover { + background: $bg-color-dark; + } + } + } + } + + // Scollable tables + &.table-scroll { + display: block; + overflow-x: auto; + padding-bottom: .75rem; + white-space: nowrap; + } + + td, + th { + border-bottom: $border-width solid $border-color; + padding: $unit-3 $unit-2; + } + th { + border-bottom-width: $border-width-lg; + } +} diff --git a/user/themes/test/scss/spectre/_tabs.scss b/user/themes/test/scss/spectre/_tabs.scss new file mode 100755 index 0000000..0dcbaf3 --- /dev/null +++ b/user/themes/test/scss/spectre/_tabs.scss @@ -0,0 +1,66 @@ +// Tabs +.tab { + align-items: center; + border-bottom: $border-width solid $border-color; + display: flex; + flex-wrap: wrap; + list-style: none; + margin: $unit-1 0 ($unit-1 - $border-width) 0; + + .tab-item { + margin-top: 0; + + a { + border-bottom: $border-width-lg solid transparent; + color: inherit; + display: block; + margin: 0 $unit-2 0 0; + padding: $unit-2 $unit-1 $unit-2 - $border-width-lg $unit-1; + text-decoration: none; + &:focus, + &:hover { + color: $link-color; + } + } + &.active a, + a.active { + border-bottom-color: $primary-color; + color: $link-color; + } + + &.tab-action { + flex: 1 0 auto; + text-align: right; + } + + .btn-clear { + margin-top: -$unit-1; + } + } + + &.tab-block { + .tab-item { + flex: 1 0 0; + text-align: center; + + a { + margin: 0; + } + + .badge { + &[data-badge]::after { + position: absolute; + right: $unit-h; + top: $unit-h; + transform: translate(0, 0); + } + } + } + } + + &:not(.tab-block) { + .badge { + padding-right: 0; + } + } +} diff --git a/user/themes/test/scss/spectre/_tiles.scss b/user/themes/test/scss/spectre/_tiles.scss new file mode 100755 index 0000000..742bbae --- /dev/null +++ b/user/themes/test/scss/spectre/_tiles.scss @@ -0,0 +1,38 @@ +// Tiles +.tile { + align-content: space-between; + align-items: flex-start; + display: flex; + + .tile-icon, + .tile-action { + flex: 0 0 auto; + } + .tile-content { + flex: 1 1 auto; + &:not(:first-child) { + padding-left: $unit-2; + } + &:not(:last-child) { + padding-right: $unit-2; + } + } + .tile-title, + .tile-subtitle { + line-height: $line-height; + } + + &.tile-centered { + align-items: center; + + .tile-content { + overflow: hidden; + } + + .tile-title, + .tile-subtitle { + @include text-ellipsis(); + margin-bottom: 0; + } + } +} diff --git a/user/themes/test/scss/spectre/_timelines.scss b/user/themes/test/scss/spectre/_timelines.scss new file mode 100755 index 0000000..c56746d --- /dev/null +++ b/user/themes/test/scss/spectre/_timelines.scss @@ -0,0 +1,56 @@ +// Timelines +.timeline { + .timeline-item { + display: flex; + margin-bottom: $unit-6; + position: relative; + &::before { + background: $border-color; + content: ""; + height: 100%; + left: 11px; + position: absolute; + top: $unit-6; + width: 2px; + } + + .timeline-left { + flex: 0 0 auto; + } + + .timeline-content { + flex: 1 1 auto; + padding: 2px 0 2px $layout-spacing-lg; + } + + .timeline-icon { + align-items: center; + border-radius: 50%; + color: $light-color; + display: flex; + height: $unit-6; + justify-content: center; + text-align: center; + width: $unit-6; + &::before { + border: $border-width-lg solid $primary-color; + border-radius: 50%; + content: ""; + display: block; + height: $unit-2; + left: $unit-2; + position: absolute; + top: $unit-2; + width: $unit-2; + } + + &.icon-lg { + background: $primary-color; + line-height: $line-height; + &::before { + content: none; + } + } + } + } +} diff --git a/user/themes/test/scss/spectre/_toasts.scss b/user/themes/test/scss/spectre/_toasts.scss new file mode 100755 index 0000000..fef15f8 --- /dev/null +++ b/user/themes/test/scss/spectre/_toasts.scss @@ -0,0 +1,48 @@ +// Toasts +.toast { + @include toast-variant($dark-color); + border: $border-width solid $dark-color; + border-radius: $border-radius; + color: $light-color; + display: block; + padding: $layout-spacing; + width: 100%; + + &.toast-primary { + @include toast-variant($primary-color); + } + + &.toast-success { + @include toast-variant($success-color); + } + + &.toast-warning { + @include toast-variant($warning-color); + } + + &.toast-error { + @include toast-variant($error-color); + } + + a { + color: $light-color; + text-decoration: underline; + + &:focus, + &:hover, + &:active, + &.active { + opacity: .75; + } + } + + .btn-clear { + margin: $unit-h; + } + + p { + &:last-child { + margin-bottom: 0; + } + } +} diff --git a/user/themes/test/scss/spectre/_tooltips.scss b/user/themes/test/scss/spectre/_tooltips.scss new file mode 100755 index 0000000..8693b67 --- /dev/null +++ b/user/themes/test/scss/spectre/_tooltips.scss @@ -0,0 +1,79 @@ +// Tooltips +.tooltip { + position: relative; + &::after { + background: rgba($dark-color, .95); + border-radius: $border-radius; + bottom: 100%; + color: $light-color; + content: attr(data-tooltip); + display: block; + font-size: $font-size-sm; + left: 50%; + max-width: $control-width-sm; + opacity: 0; + overflow: hidden; + padding: $unit-1 $unit-2; + pointer-events: none; + position: absolute; + text-overflow: ellipsis; + transform: translate(-50%, $unit-2); + transition: opacity .2s, transform .2s; + white-space: pre; + z-index: $zindex-3; + } + &:focus, + &:hover { + &::after { + opacity: 1; + transform: translate(-50%, -$unit-1); + } + } + &[disabled], + &.disabled { + pointer-events: auto; + } + + &.tooltip-right { + &::after { + bottom: 50%; + left: 100%; + transform: translate(-$unit-1, 50%); + } + &:focus, + &:hover { + &::after { + transform: translate($unit-1, 50%); + } + } + } + + &.tooltip-bottom { + &::after { + bottom: auto; + top: 100%; + transform: translate(-50%, -$unit-2); + } + &:focus, + &:hover { + &::after { + transform: translate(-50%, $unit-1); + } + } + } + + &.tooltip-left { + &::after { + bottom: 50%; + left: auto; + right: 100%; + transform: translate($unit-2, 50%); + } + &:focus, + &:hover { + &::after { + transform: translate(-$unit-1, 50%); + } + } + } +} diff --git a/user/themes/test/scss/spectre/_typography.scss b/user/themes/test/scss/spectre/_typography.scss new file mode 100755 index 0000000..bbeb876 --- /dev/null +++ b/user/themes/test/scss/spectre/_typography.scss @@ -0,0 +1,129 @@ +// Typography +// Headings +h1, +h2, +h3, +h4, +h5, +h6 { + color: inherit; + font-weight: 500; + line-height: 1.2; + margin-bottom: .5em; + margin-top: 0; +} +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + font-weight: 500; +} +h1, +.h1 { + font-size: 2rem; +} +h2, +.h2 { + font-size: 1.6rem; +} +h3, +.h3 { + font-size: 1.4rem; +} +h4, +.h4 { + font-size: 1.2rem; +} +h5, +.h5 { + font-size: 1rem; +} +h6, +.h6 { + font-size: .8rem; +} + +// Paragraphs +p { + margin: 0 0 $line-height; +} + +// Semantic text elements +a, +ins, +u { + text-decoration-skip: ink edges; +} + +abbr[title] { + border-bottom: $border-width dotted; + cursor: help; + text-decoration: none; +} + +kbd { + @include label-base(); + @include label-variant($light-color, $dark-color); + font-size: $font-size-sm; +} + +mark { + @include label-variant($body-font-color, $highlight-color); + border-bottom: $unit-o solid darken($highlight-color, 15%); + border-radius: $border-radius; + padding: $unit-o $unit-h 0; +} + +// Blockquote +blockquote { + border-left: $border-width-lg solid $border-color; + margin-left: 0; + padding: $unit-2 $unit-4; + + p:last-child { + margin-bottom: 0; + } +} + +// Lists +ul, +ol { + margin: $unit-4 0 $unit-4 $unit-4; + padding: 0; + + ul, + ol { + margin: $unit-4 0 $unit-4 $unit-4; + } + + li { + margin-top: $unit-2; + } +} + +ul { + list-style: disc inside; + + ul { + list-style-type: circle; + } +} + +ol { + list-style: decimal inside; + + ol { + list-style-type: lower-alpha; + } +} + +dl { + dt { + font-weight: bold; + } + dd { + margin: $unit-2 0 $unit-4 0; + } +} diff --git a/user/themes/test/scss/spectre/_utilities.scss b/user/themes/test/scss/spectre/_utilities.scss new file mode 100755 index 0000000..80f1e0b --- /dev/null +++ b/user/themes/test/scss/spectre/_utilities.scss @@ -0,0 +1,8 @@ +@import "utilities/colors"; +@import "utilities/cursors"; +@import "utilities/display"; +@import "utilities/divider"; +@import "utilities/loading"; +@import "utilities/position"; +@import "utilities/shapes"; +@import "utilities/text"; diff --git a/user/themes/test/scss/spectre/_variables.scss b/user/themes/test/scss/spectre/_variables.scss new file mode 100755 index 0000000..7bf1a13 --- /dev/null +++ b/user/themes/test/scss/spectre/_variables.scss @@ -0,0 +1,117 @@ +// Core variables +$version: "0.5.8"; + +// Core features +$rtl: false !default; + +// Core colors +$primary-color: #5755d9 !default; +$primary-color-dark: darken($primary-color, 3%) !default; +$primary-color-light: lighten($primary-color, 3%) !default; +$secondary-color: lighten($primary-color, 37.5%) !default; +$secondary-color-dark: darken($secondary-color, 3%) !default; +$secondary-color-light: lighten($secondary-color, 3%) !default; + +// Gray colors +$dark-color: #303742 !default; +$light-color: #fff !default; +$gray-color: lighten($dark-color, 55%) !default; +$gray-color-dark: darken($gray-color, 30%) !default; +$gray-color-light: lighten($gray-color, 20%) !default; + +$border-color: lighten($dark-color, 65%) !default; +$border-color-dark: darken($border-color, 10%) !default; +$border-color-light: lighten($border-color, 8%) !default; +$bg-color: lighten($dark-color, 75%) !default; +$bg-color-dark: darken($bg-color, 3%) !default; +$bg-color-light: $light-color !default; + +// Control colors +$success-color: #32b643 !default; +$warning-color: #ffb700 !default; +$error-color: #e85600 !default; + +// Other colors +$code-color: #d73e48 !default; +$highlight-color: #ffe9b3 !default; +$body-bg: $bg-color-light !default; +$body-font-color: lighten($dark-color, 5%) !default; +$link-color: $primary-color !default; +$link-color-dark: darken($link-color, 10%) !default; +$link-color-light: lighten($link-color, 10%) !default; + +// Fonts +// Credit: https://www.smashingmagazine.com/2015/11/using-system-ui-fonts-practical-guide/ +$base-font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto !default; +$mono-font-family: "SF Mono", "Segoe UI Mono", "Roboto Mono", Menlo, Courier, monospace !default; +$fallback-font-family: "Helvetica Neue", sans-serif !default; +$cjk-zh-hans-font-family: $base-font-family, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", $fallback-font-family !default; +$cjk-zh-hant-font-family: $base-font-family, "PingFang TC", "Hiragino Sans CNS", "Microsoft JhengHei", $fallback-font-family !default; +$cjk-jp-font-family: $base-font-family, "Hiragino Sans", "Hiragino Kaku Gothic Pro", "Yu Gothic", YuGothic, Meiryo, $fallback-font-family !default; +$cjk-ko-font-family: $base-font-family, "Malgun Gothic", $fallback-font-family !default; +$body-font-family: $base-font-family, $fallback-font-family !default; + +// Unit sizes +$unit-o: .05rem !default; +$unit-h: .1rem !default; +$unit-1: .2rem !default; +$unit-2: .4rem !default; +$unit-3: .6rem !default; +$unit-4: .8rem !default; +$unit-5: 1rem !default; +$unit-6: 1.2rem !default; +$unit-7: 1.4rem !default; +$unit-8: 1.6rem !default; +$unit-9: 1.8rem !default; +$unit-10: 2rem !default; +$unit-12: 2.4rem !default; +$unit-16: 3.2rem !default; + +// Font sizes +$html-font-size: 20px !default; +$html-line-height: 1.5 !default; +$font-size: .8rem !default; +$font-size-sm: .7rem !default; +$font-size-lg: .9rem !default; +$line-height: 1.2rem !default; + +// Sizes +$layout-spacing: $unit-2 !default; +$layout-spacing-sm: $unit-1 !default; +$layout-spacing-lg: $unit-4 !default; +$border-radius: $unit-h !default; +$border-width: $unit-o !default; +$border-width-lg: $unit-h !default; +$control-size: $unit-9 !default; +$control-size-sm: $unit-7 !default; +$control-size-lg: $unit-10 !default; +$control-padding-x: $unit-2 !default; +$control-padding-x-sm: $unit-2 * .75 !default; +$control-padding-x-lg: $unit-2 * 1.5 !default; +$control-padding-y: ($control-size - $line-height) / 2 - $border-width !default; +$control-padding-y-sm: ($control-size-sm - $line-height) / 2 - $border-width !default; +$control-padding-y-lg: ($control-size-lg - $line-height) / 2 - $border-width !default; +$control-icon-size: .8rem !default; + +$control-width-xs: 180px !default; +$control-width-sm: 320px !default; +$control-width-md: 640px !default; +$control-width-lg: 960px !default; +$control-width-xl: 1280px !default; + +// Responsive breakpoints +$size-xs: 480px !default; +$size-sm: 600px !default; +$size-md: 840px !default; +$size-lg: 960px !default; +$size-xl: 1280px !default; +$size-2x: 1440px !default; + +$responsive-breakpoint: $size-xs !default; + +// Z-index +$zindex-0: 1 !default; +$zindex-1: 100 !default; +$zindex-2: 200 !default; +$zindex-3: 300 !default; +$zindex-4: 400 !default; diff --git a/user/themes/test/scss/spectre/_viewer-360.scss b/user/themes/test/scss/spectre/_viewer-360.scss new file mode 100755 index 0000000..c1b8928 --- /dev/null +++ b/user/themes/test/scss/spectre/_viewer-360.scss @@ -0,0 +1,34 @@ +// 360 Degree Viewer + +// Mixin: Viewer slider sizes +@mixin viewer-slider-size($image-number: 36) { + @for $s from 1 through ($image-number) { + .viewer-slider[max='#{$image-number}'][value='#{$s}'] + .viewer-image { + background-position-y: percentage((($s)-1) * 1/(($image-number)-1)); + } + } +} + +.viewer-360 { + align-items: center; + display: flex; + flex-direction: column; + + // Copy and add more numbers if you need + @include viewer-slider-size(36); + + .viewer-slider { + cursor: ew-resize; + margin: 1rem; + order: 2; + width: 60%; + } + + .viewer-image { + background-position-y: 0; + background-repeat: no-repeat; + background-size: 100%; + max-width: 100%; + order: 1; + } +} \ No newline at end of file diff --git a/user/themes/test/scss/spectre/icons/_icons-action.scss b/user/themes/test/scss/spectre/icons/_icons-action.scss new file mode 100755 index 0000000..1b952ea --- /dev/null +++ b/user/themes/test/scss/spectre/icons/_icons-action.scss @@ -0,0 +1,315 @@ +// Icon resize +.icon-resize-horiz, +.icon-resize-vert { + &::before, + &::after { + border: $icon-border-width solid currentColor; + border-bottom: 0; + border-right: 0; + height: .45em; + width: .45em; + } + &::before { + transform: translate(-50%, -90%) rotate(45deg); + } + &::after { + transform: translate(-50%, -10%) rotate(225deg); + } +} + +.icon-resize-horiz { + &::before { + transform: translate(-90%, -50%) rotate(-45deg); + } + &::after { + transform: translate(-10%, -50%) rotate(135deg); + } +} + +// Icon more +.icon-more-horiz, +.icon-more-vert { + &::before { + background: currentColor; + box-shadow: -.4em 0, .4em 0; + border-radius: 50%; + height: 3px; + width: 3px; + } +} + +.icon-more-vert { + &::before { + box-shadow: 0 -.4em, 0 .4em; + } +} + +// Icon plus, minus, cross +.icon-plus, +.icon-minus, +.icon-cross { + &::before { + background: currentColor; + height: $icon-border-width; + width: 100%; + } +} + +.icon-plus, +.icon-cross { + &::after { + background: currentColor; + height: 100%; + width: $icon-border-width; + } +} + +.icon-cross { + &::before { + width: 100%; + } + &::after { + height: 100%; + } + &::before, + &::after { + transform: translate(-50%, -50%) rotate(45deg); + } +} + +// Icon check +.icon-check { + &::before { + border: $icon-border-width solid currentColor; + border-right: 0; + border-top: 0; + height: .5em; + width: .9em; + transform: translate(-50%, -75%) rotate(-45deg); + } +} + +// Icon stop +.icon-stop { + border: $icon-border-width solid currentColor; + border-radius: 50%; + &::before { + background: currentColor; + height: $icon-border-width; + transform: translate(-50%, -50%) rotate(45deg); + width: 1em; + } +} + +// Icon shutdown +.icon-shutdown { + border: $icon-border-width solid currentColor; + border-radius: 50%; + border-top-color: transparent; + &::before { + background: currentColor; + content: ""; + height: .5em; + top: .1em; + width: $icon-border-width; + } +} + +// Icon refresh +.icon-refresh { + &::before { + border: $icon-border-width solid currentColor; + border-radius: 50%; + border-right-color: transparent; + height: 1em; + width: 1em; + } + &::after { + border: .2em solid currentColor; + border-top-color: transparent; + border-left-color: transparent; + height: 0; + left: 80%; + top: 20%; + width: 0; + } +} + +// Icon search +.icon-search { + &::before { + border: $icon-border-width solid currentColor; + border-radius: 50%; + height: .75em; + left: 5%; + top: 5%; + transform: translate(0, 0) rotate(45deg); + width: .75em; + } + &::after { + background: currentColor; + height: $icon-border-width; + left: 80%; + top: 80%; + transform: translate(-50%, -50%) rotate(45deg); + width: .4em; + } +} + +// Icon edit +.icon-edit { + &::before { + border: $icon-border-width solid currentColor; + height: .4em; + transform: translate(-40%, -60%) rotate(-45deg); + width: .85em; + } + &::after { + border: .15em solid currentColor; + border-top-color: transparent; + border-right-color: transparent; + height: 0; + left: 5%; + top: 95%; + transform: translate(0, -100%); + width: 0; + } +} + +// Icon delete +.icon-delete { + &::before { + border: $icon-border-width solid currentColor; + border-bottom-left-radius: $border-radius; + border-bottom-right-radius: $border-radius; + border-top: 0; + height: .75em; + top: 60%; + width: .75em; + } + &::after { + background: currentColor; + box-shadow: -.25em .2em, .25em .2em; + height: $icon-border-width; + top: $icon-border-width/2; + width: .5em; + } +} + +// Icon share +.icon-share { + border: $icon-border-width solid currentColor; + border-radius: $border-radius; + border-right: 0; + border-top: 0; + &::before { + border: $icon-border-width solid currentColor; + border-left: 0; + border-top: 0; + height: .4em; + left: 100%; + top: .25em; + transform: translate(-125%, -50%) rotate(-45deg); + width: .4em; + } + &::after { + border: $icon-border-width solid currentColor; + border-bottom: 0; + border-right: 0; + border-radius: 75% 0; + height: .5em; + width: .6em; + } +} + +// Icon flag +.icon-flag { + &::before { + background: currentColor; + height: 1em; + left: 15%; + width: $icon-border-width; + } + &::after { + border: $icon-border-width solid currentColor; + border-bottom-right-radius: $border-radius; + border-left: 0; + border-top-right-radius: $border-radius; + height: .65em; + top: 35%; + left: 60%; + width: .8em; + } +} + +// Icon bookmark +.icon-bookmark { + &::before { + border: $icon-border-width solid currentColor; + border-bottom: 0; + border-top-left-radius: $border-radius; + border-top-right-radius: $border-radius; + height: .9em; + width: .8em; + } + &::after { + border: $icon-border-width solid currentColor; + border-bottom: 0; + border-left: 0; + border-radius: $border-radius; + height: .5em; + transform: translate(-50%, 35%) rotate(-45deg) skew(15deg, 15deg); + width: .5em; + } +} + +// Icon download & upload +.icon-download, +.icon-upload { + border-bottom: $icon-border-width solid currentColor; + &::before { + border: $icon-border-width solid currentColor; + border-bottom: 0; + border-right: 0; + height: .5em; + width: .5em; + transform: translate(-50%, -60%) rotate(-135deg); + } + &::after { + background: currentColor; + height: .6em; + top: 40%; + width: $icon-border-width; + } +} + +.icon-upload { + &::before { + transform: translate(-50%, -60%) rotate(45deg); + } + &::after { + top: 50%; + } +} + +// Icon copy +.icon-copy { + &::before { + border: $icon-border-width solid currentColor; + border-radius: $border-radius; + border-right: 0; + border-bottom: 0; + height: .8em; + left: 40%; + top: 35%; + width: .8em; + } + &::after { + border: $icon-border-width solid currentColor; + border-radius: $border-radius; + height: .8em; + left: 60%; + top: 60%; + width: .8em; + } +} \ No newline at end of file diff --git a/user/themes/test/scss/spectre/icons/_icons-core.scss b/user/themes/test/scss/spectre/icons/_icons-core.scss new file mode 100755 index 0000000..9a67ae4 --- /dev/null +++ b/user/themes/test/scss/spectre/icons/_icons-core.scss @@ -0,0 +1,54 @@ +// Icon variables +$icon-border-width: $border-width-lg; +$icon-prefix: "icon"; + +// Icon base style +.#{$icon-prefix} { + box-sizing: border-box; + display: inline-block; + font-size: inherit; + font-style: normal; + height: 1em; + position: relative; + text-indent: -9999px; + vertical-align: middle; + width: 1em; + &::before, + &::after { + content: ""; + display: block; + left: 50%; + position: absolute; + top: 50%; + transform: translate(-50%, -50%); + } + + // Icon sizes + &.icon-2x { + font-size: 1.6rem; + } + + &.icon-3x { + font-size: 2.4rem; + } + + &.icon-4x { + font-size: 3.2rem; + } +} + +// Component icon support +.accordion, +.btn, +.toast, +.menu { + .#{$icon-prefix} { + vertical-align: -10%; + } +} + +.btn-lg { + .#{$icon-prefix} { + vertical-align: -15%; + } +} diff --git a/user/themes/test/scss/spectre/icons/_icons-navigation.scss b/user/themes/test/scss/spectre/icons/_icons-navigation.scss new file mode 100755 index 0000000..92ab231 --- /dev/null +++ b/user/themes/test/scss/spectre/icons/_icons-navigation.scss @@ -0,0 +1,127 @@ +// Icon arrows +.icon-arrow-down, +.icon-arrow-left, +.icon-arrow-right, +.icon-arrow-up, +.icon-downward, +.icon-back, +.icon-forward, +.icon-upward { + &::before { + border: $icon-border-width solid currentColor; + border-bottom: 0; + border-right: 0; + height: .65em; + width: .65em; + } +} + +.icon-arrow-down { + &::before { + transform: translate(-50%, -75%) rotate(225deg); + } +} + +.icon-arrow-left { + &::before { + transform: translate(-25%, -50%) rotate(-45deg); + } +} + +.icon-arrow-right { + &::before { + transform: translate(-75%, -50%) rotate(135deg); + } +} + +.icon-arrow-up { + &::before { + transform: translate(-50%, -25%) rotate(45deg); + } +} + +.icon-back, +.icon-forward { + &::after { + background: currentColor; + height: $icon-border-width; + width: .8em; + } +} + +.icon-downward, +.icon-upward { + &::after { + background: currentColor; + height: .8em; + width: $icon-border-width; + } +} + +.icon-back { + &::after { + left: 55%; + } + &::before { + transform: translate(-50%, -50%) rotate(-45deg); + } +} + +.icon-downward { + &::after { + top: 45%; + } + &::before { + transform: translate(-50%, -50%) rotate(-135deg); + } +} + +.icon-forward { + &::after { + left: 45%; + } + &::before { + transform: translate(-50%, -50%) rotate(135deg); + } +} + +.icon-upward { + &::after { + top: 55%; + } + &::before { + transform: translate(-50%, -50%) rotate(45deg); + } +} + +// Icon caret +.icon-caret { + &::before { + border-top: .3em solid currentColor; + border-right: .3em solid transparent; + border-left: .3em solid transparent; + height: 0; + transform: translate(-50%, -25%); + width: 0; + } +} + +// Icon menu +.icon-menu { + &::before { + background: currentColor; + box-shadow: 0 -.35em, 0 .35em; + height: $icon-border-width; + width: 100%; + } +} + +// Icon apps +.icon-apps { + &::before { + background: currentColor; + box-shadow: -.35em -.35em, -.35em 0, -.35em .35em, 0 -.35em, 0 .35em, .35em -.35em, .35em 0, .35em .35em; + height: 3px; + width: 3px; + } +} diff --git a/user/themes/test/scss/spectre/icons/_icons-object.scss b/user/themes/test/scss/spectre/icons/_icons-object.scss new file mode 100755 index 0000000..00597d8 --- /dev/null +++ b/user/themes/test/scss/spectre/icons/_icons-object.scss @@ -0,0 +1,161 @@ +// Icon time +.icon-time { + border: $icon-border-width solid currentColor; + border-radius: 50%; + &::before { + background: currentColor; + height: .4em; + transform: translate(-50%, -75%); + width: $icon-border-width; + } + &::after { + background: currentColor; + height: .3em; + transform: translate(-50%, -75%) rotate(90deg); + transform-origin: 50% 90%; + width: $icon-border-width; + } +} + +// Icon mail +.icon-mail { + &::before { + border: $icon-border-width solid currentColor; + border-radius: $border-radius; + height: .8em; + width: 1em; + } + &::after { + border: $icon-border-width solid currentColor; + border-right: 0; + border-top: 0; + height: .5em; + transform: translate(-50%, -90%) rotate(-45deg) skew(10deg, 10deg); + width: .5em; + } +} + +// Icon people +.icon-people { + &::before { + border: $icon-border-width solid currentColor; + border-radius: 50%; + height: .45em; + top: 25%; + width: .45em; + } + &::after { + border: $icon-border-width solid currentColor; + border-radius: 50% 50% 0 0; + height: .4em; + top: 75%; + width: .9em; + } +} + +// Icon message +.icon-message { + border: $icon-border-width solid currentColor; + border-bottom: 0; + border-radius: $border-radius; + border-right: 0; + &::before { + border: $icon-border-width solid currentColor; + border-bottom-right-radius: $border-radius; + border-left: 0; + border-top: 0; + height: .8em; + left: 65%; + top: 40%; + width: .7em; + } + &::after { + background: currentColor; + border-radius: $border-radius; + height: .3em; + left: 10%; + top: 100%; + transform: translate(0, -90%) rotate(45deg); + width: $icon-border-width; + } +} + +// Icon photo +.icon-photo { + border: $icon-border-width solid currentColor; + border-radius: $border-radius; + &::before { + border: $icon-border-width solid currentColor; + border-radius: 50%; + height: .25em; + left: 35%; + top: 35%; + width: .25em; + } + &::after { + border: $icon-border-width solid currentColor; + border-bottom: 0; + border-left: 0; + height: .5em; + left: 60%; + transform: translate(-50%, 25%) rotate(-45deg); + width: .5em; + } +} + +// Icon link +.icon-link { + &::before, + &::after { + border: $icon-border-width solid currentColor; + border-radius: 5em 0 0 5em; + border-right: 0; + height: .5em; + width: .75em; + } + &::before { + transform: translate(-70%, -45%) rotate(-45deg); + } + &::after { + transform: translate(-30%, -55%) rotate(135deg); + } +} + +// Icon location +.icon-location { + &::before { + border: $icon-border-width solid currentColor; + border-radius: 50% 50% 50% 0; + height: .8em; + transform: translate(-50%, -60%) rotate(-45deg); + width: .8em; + } + &::after { + border: $icon-border-width solid currentColor; + border-radius: 50%; + height: .2em; + transform: translate(-50%, -80%); + width: .2em; + } +} + +// Icon emoji +.icon-emoji { + border: $icon-border-width solid currentColor; + border-radius: 50%; + &::before { + border-radius: 50%; + box-shadow: -.17em -.1em, .17em -.1em; + height: .15em; + width: .15em; + } + &::after { + border: $icon-border-width solid currentColor; + border-bottom-color: transparent; + border-radius: 50%; + border-right-color: transparent; + height: .5em; + transform: translate(-50%, -40%) rotate(-135deg); + width: .5em; + } +} diff --git a/user/themes/test/scss/spectre/mixins/_avatar.scss b/user/themes/test/scss/spectre/mixins/_avatar.scss new file mode 100755 index 0000000..14617ad --- /dev/null +++ b/user/themes/test/scss/spectre/mixins/_avatar.scss @@ -0,0 +1,6 @@ +// Avatar mixin +@mixin avatar-base($size: $unit-8) { + font-size: $size / 2; + height: $size; + width: $size; +} diff --git a/user/themes/test/scss/spectre/mixins/_button.scss b/user/themes/test/scss/spectre/mixins/_button.scss new file mode 100755 index 0000000..c90a94b --- /dev/null +++ b/user/themes/test/scss/spectre/mixins/_button.scss @@ -0,0 +1,54 @@ +// Button variant mixin +@mixin button-variant($color: $primary-color) { + background: $color; + border-color: darken($color, 3%); + color: $light-color; + &:focus { + @include control-shadow($color); + } + &:focus, + &:hover { + background: darken($color, 2%); + border-color: darken($color, 5%); + color: $light-color; + } + &:active, + &.active { + background: darken($color, 7%); + border-color: darken($color, 10%); + color: $light-color; + } + &.loading { + &::after { + border-bottom-color: $light-color; + border-left-color: $light-color; + } + } +} + +@mixin button-outline-variant($color: $primary-color) { + background: $light-color; + border-color: $color; + color: $color; + &:focus { + @include control-shadow($color); + } + &:focus, + &:hover { + background: lighten($color, 50%); + border-color: darken($color, 2%); + color: $color; + } + &:active, + &.active { + background: $color; + border-color: darken($color, 5%); + color: $light-color; + } + &.loading { + &::after { + border-bottom-color: $color; + border-left-color: $color; + } + } +} diff --git a/user/themes/test/scss/spectre/mixins/_clearfix.scss b/user/themes/test/scss/spectre/mixins/_clearfix.scss new file mode 100755 index 0000000..db6895f --- /dev/null +++ b/user/themes/test/scss/spectre/mixins/_clearfix.scss @@ -0,0 +1,8 @@ +// Clearfix mixin +@mixin clearfix() { + &::after { + clear: both; + content: ""; + display: table; + } +} diff --git a/user/themes/test/scss/spectre/mixins/_color.scss b/user/themes/test/scss/spectre/mixins/_color.scss new file mode 100755 index 0000000..697d0c3 --- /dev/null +++ b/user/themes/test/scss/spectre/mixins/_color.scss @@ -0,0 +1,27 @@ +// Background color utility mixin +@mixin bg-color-variant($name: ".bg-primary", $color: $primary-color) { + #{$name} { + background: $color !important; + + @if (lightness($color) < 60) { + color: $light-color; + } + } +} + +// Text color utility mixin +@mixin text-color-variant($name: ".text-primary", $color: $primary-color) { + #{$name} { + color: $color !important; + } + + a#{$name} { + &:focus, + &:hover { + color: darken($color, 5%); + } + &:visited { + color: lighten($color, 5%); + } + } +} diff --git a/user/themes/test/scss/spectre/mixins/_label.scss b/user/themes/test/scss/spectre/mixins/_label.scss new file mode 100755 index 0000000..1574f02 --- /dev/null +++ b/user/themes/test/scss/spectre/mixins/_label.scss @@ -0,0 +1,11 @@ +// Label base style +@mixin label-base() { + border-radius: $border-radius; + line-height: 1.25; + padding: .1rem .2rem; +} + +@mixin label-variant($color: $light-color, $bg-color: $primary-color) { + background: $bg-color; + color: $color; +} diff --git a/user/themes/test/scss/spectre/mixins/_position.scss b/user/themes/test/scss/spectre/mixins/_position.scss new file mode 100755 index 0000000..98b5cfc --- /dev/null +++ b/user/themes/test/scss/spectre/mixins/_position.scss @@ -0,0 +1,65 @@ +// Margin utility mixin +@mixin margin-variant($id: 1, $size: $unit-1) { + .m-#{$id} { + margin: $size !important; + } + + .mb-#{$id} { + margin-bottom: $size !important; + } + + .ml-#{$id} { + margin-left: $size !important; + } + + .mr-#{$id} { + margin-right: $size !important; + } + + .mt-#{$id} { + margin-top: $size !important; + } + + .mx-#{$id} { + margin-left: $size !important; + margin-right: $size !important; + } + + .my-#{$id} { + margin-bottom: $size !important; + margin-top: $size !important; + } +} + +// Padding utility mixin +@mixin padding-variant($id: 1, $size: $unit-1) { + .p-#{$id} { + padding: $size !important; + } + + .pb-#{$id} { + padding-bottom: $size !important; + } + + .pl-#{$id} { + padding-left: $size !important; + } + + .pr-#{$id} { + padding-right: $size !important; + } + + .pt-#{$id} { + padding-top: $size !important; + } + + .px-#{$id} { + padding-left: $size !important; + padding-right: $size !important; + } + + .py-#{$id} { + padding-bottom: $size !important; + padding-top: $size !important; + } +} diff --git a/user/themes/test/scss/spectre/mixins/_shadow.scss b/user/themes/test/scss/spectre/mixins/_shadow.scss new file mode 100755 index 0000000..7984449 --- /dev/null +++ b/user/themes/test/scss/spectre/mixins/_shadow.scss @@ -0,0 +1,9 @@ +// Component focus shadow +@mixin control-shadow($color: $primary-color) { + box-shadow: 0 0 0 .1rem rgba($color, .2); +} + +// Shadow mixin +@mixin shadow-variant($offset) { + box-shadow: 0 $offset ($offset + .05rem) * 2 rgba($dark-color, .3); +} diff --git a/user/themes/test/scss/spectre/mixins/_text.scss b/user/themes/test/scss/spectre/mixins/_text.scss new file mode 100755 index 0000000..97dc99d --- /dev/null +++ b/user/themes/test/scss/spectre/mixins/_text.scss @@ -0,0 +1,6 @@ +// Text Ellipsis +@mixin text-ellipsis() { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/user/themes/test/scss/spectre/mixins/_toast.scss b/user/themes/test/scss/spectre/mixins/_toast.scss new file mode 100755 index 0000000..fa2bb13 --- /dev/null +++ b/user/themes/test/scss/spectre/mixins/_toast.scss @@ -0,0 +1,5 @@ +// Toast variant mixin +@mixin toast-variant($color: $dark-color) { + background: rgba($color, .95); + border-color: $color; +} diff --git a/user/themes/test/scss/spectre/spectre-exp.scss b/user/themes/test/scss/spectre/spectre-exp.scss new file mode 100755 index 0000000..33ed3fe --- /dev/null +++ b/user/themes/test/scss/spectre/spectre-exp.scss @@ -0,0 +1,18 @@ +// Variables and mixins +@import "variables"; +@import "mixins"; + +/*! Spectre.css Experimentals v#{$version} | MIT License | github.com/picturepan2/spectre */ +// Experimentals +@import "autocomplete"; +@import "calendars"; +@import "carousels"; +@import "comparison-sliders"; +@import "filters"; +@import "meters"; +@import "off-canvas"; +@import "parallax"; +@import "progress"; +@import "sliders"; +@import "timelines"; +@import "viewer-360"; diff --git a/user/themes/test/scss/spectre/spectre-icons.scss b/user/themes/test/scss/spectre/spectre-icons.scss new file mode 100755 index 0000000..383624e --- /dev/null +++ b/user/themes/test/scss/spectre/spectre-icons.scss @@ -0,0 +1,10 @@ +// Variables and mixins +@import "variables"; +@import "mixins"; + +/*! Spectre.css Icons v#{$version} | MIT License | github.com/picturepan2/spectre */ +// Icons +@import "icons/icons-core"; +@import "icons/icons-navigation"; +@import "icons/icons-action"; +@import "icons/icons-object"; diff --git a/user/themes/test/scss/spectre/spectre.scss b/user/themes/test/scss/spectre/spectre.scss new file mode 100755 index 0000000..cff1fde --- /dev/null +++ b/user/themes/test/scss/spectre/spectre.scss @@ -0,0 +1,49 @@ +// Variables and mixins +@import "variables"; +@import "mixins"; + +/*! Spectre.css v#{$version} | MIT License | github.com/picturepan2/spectre */ +// Reset and dependencies +@import "normalize"; +@import "base"; + +// Elements +@import "typography"; +@import "asian"; +@import "tables"; +@import "buttons"; +@import "forms"; +@import "labels"; +@import "codes"; +@import "media"; + +// Layout +@import "layout"; +@import "hero"; +@import "navbar"; + +// Components +@import "accordions"; +@import "avatars"; +@import "badges"; +@import "breadcrumbs"; +@import "bars"; +@import "cards"; +@import "chips"; +@import "dropdowns"; +@import "empty"; +@import "menus"; +@import "modals"; +@import "navs"; +@import "pagination"; +@import "panels"; +@import "popovers"; +@import "steps"; +@import "tabs"; +@import "tiles"; +@import "toasts"; +@import "tooltips"; + +// Utility classes +@import "animations"; +@import "utilities"; diff --git a/user/themes/test/scss/spectre/utilities/_colors.scss b/user/themes/test/scss/spectre/utilities/_colors.scss new file mode 100755 index 0000000..28dd221 --- /dev/null +++ b/user/themes/test/scss/spectre/utilities/_colors.scss @@ -0,0 +1,31 @@ +// Text colors +@include text-color-variant(".text-primary", $primary-color); + +@include text-color-variant(".text-secondary", $secondary-color-dark); + +@include text-color-variant(".text-gray", $gray-color); + +@include text-color-variant(".text-light", $light-color); + +@include text-color-variant(".text-dark", $body-font-color); + +@include text-color-variant(".text-success", $success-color); + +@include text-color-variant(".text-warning", $warning-color); + +@include text-color-variant(".text-error", $error-color); + +// Background colors +@include bg-color-variant(".bg-primary", $primary-color); + +@include bg-color-variant(".bg-secondary", $secondary-color); + +@include bg-color-variant(".bg-dark", $dark-color); + +@include bg-color-variant(".bg-gray", $bg-color); + +@include bg-color-variant(".bg-success", $success-color); + +@include bg-color-variant(".bg-warning", $warning-color); + +@include bg-color-variant(".bg-error", $error-color); diff --git a/user/themes/test/scss/spectre/utilities/_cursors.scss b/user/themes/test/scss/spectre/utilities/_cursors.scss new file mode 100755 index 0000000..bd755c8 --- /dev/null +++ b/user/themes/test/scss/spectre/utilities/_cursors.scss @@ -0,0 +1,24 @@ +// Cursors +.c-hand { + cursor: pointer; +} + +.c-move { + cursor: move; +} + +.c-zoom-in { + cursor: zoom-in; +} + +.c-zoom-out { + cursor: zoom-out; +} + +.c-not-allowed { + cursor: not-allowed; +} + +.c-auto { + cursor: auto; +} \ No newline at end of file diff --git a/user/themes/test/scss/spectre/utilities/_display.scss b/user/themes/test/scss/spectre/utilities/_display.scss new file mode 100755 index 0000000..c6248e0 --- /dev/null +++ b/user/themes/test/scss/spectre/utilities/_display.scss @@ -0,0 +1,44 @@ +// Display +.d-block { + display: block; +} +.d-inline { + display: inline; +} +.d-inline-block { + display: inline-block; +} +.d-flex { + display: flex; +} +.d-inline-flex { + display: inline-flex; +} +.d-none, +.d-hide { + display: none !important; +} +.d-visible { + visibility: visible; +} +.d-invisible { + visibility: hidden; +} +.text-hide { + background: transparent; + border: 0; + color: transparent; + font-size: 0; + line-height: 0; + text-shadow: none; +} +.text-assistive { + border: 0; + clip: rect(0,0,0,0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} diff --git a/user/themes/test/scss/spectre/utilities/_divider.scss b/user/themes/test/scss/spectre/utilities/_divider.scss new file mode 100755 index 0000000..e6c09d2 --- /dev/null +++ b/user/themes/test/scss/spectre/utilities/_divider.scss @@ -0,0 +1,50 @@ +// Divider +.divider, +.divider-vert { + display: block; + position: relative; + + &[data-content]::after { + background: $bg-color-light; + color: $gray-color; + content: attr(data-content); + display: inline-block; + font-size: $font-size-sm; + padding: 0 $unit-2; + transform: translateY(-$font-size-sm + $border-width); + } +} + +.divider { + border-top: $border-width solid $border-color-light; + height: $border-width; + margin: $unit-2 0; + + &[data-content] { + margin: $unit-4 0; + } +} + +.divider-vert { + display: block; + padding: $unit-4; + + &::before { + border-left: $border-width solid $border-color; + bottom: $unit-2; + content: ""; + display: block; + left: 50%; + position: absolute; + top: $unit-2; + transform: translateX(-50%); + } + + &[data-content]::after { + left: 50%; + padding: $unit-1 0; + position: absolute; + top: 50%; + transform: translate(-50%, -50%); + } +} diff --git a/user/themes/test/scss/spectre/utilities/_loading.scss b/user/themes/test/scss/spectre/utilities/_loading.scss new file mode 100755 index 0000000..1b4ea60 --- /dev/null +++ b/user/themes/test/scss/spectre/utilities/_loading.scss @@ -0,0 +1,34 @@ +// Loading +.loading { + color: transparent !important; + min-height: $unit-4; + pointer-events: none; + position: relative; + &::after { + animation: loading 500ms infinite linear; + border: $border-width-lg solid $primary-color; + border-radius: 50%; + border-right-color: transparent; + border-top-color: transparent; + content: ""; + display: block; + height: $unit-4; + left: 50%; + margin-left: -$unit-2; + margin-top: -$unit-2; + position: absolute; + top: 50%; + width: $unit-4; + z-index: $zindex-0; + } + + &.loading-lg { + min-height: $unit-10; + &::after { + height: $unit-8; + margin-left: -$unit-4; + margin-top: -$unit-4; + width: $unit-8; + } + } +} diff --git a/user/themes/test/scss/spectre/utilities/_position.scss b/user/themes/test/scss/spectre/utilities/_position.scss new file mode 100755 index 0000000..c1a7f75 --- /dev/null +++ b/user/themes/test/scss/spectre/utilities/_position.scss @@ -0,0 +1,54 @@ +// Position +.clearfix { + @include clearfix(); +} + +.float-left { + float: left !important; +} + +.float-right { + float: right !important; +} + +.p-relative { + position: relative !important; +} + +.p-absolute { + position: absolute !important; +} + +.p-fixed { + position: fixed !important; +} + +.p-sticky { + position: sticky !important; +} + +.p-centered { + display: block; + float: none; + margin-left: auto; + margin-right: auto; +} + +.flex-centered { + align-items: center; + display: flex; + justify-content: center; +} + +// Spacing +@include margin-variant(0, 0); + +@include margin-variant(1, $unit-1); + +@include margin-variant(2, $unit-2); + +@include padding-variant(0, 0); + +@include padding-variant(1, $unit-1); + +@include padding-variant(2, $unit-2); diff --git a/user/themes/test/scss/spectre/utilities/_shapes.scss b/user/themes/test/scss/spectre/utilities/_shapes.scss new file mode 100755 index 0000000..23e131e --- /dev/null +++ b/user/themes/test/scss/spectre/utilities/_shapes.scss @@ -0,0 +1,8 @@ +// Shapes +.s-rounded { + border-radius: $border-radius; +} + +.s-circle { + border-radius: 50%; +} \ No newline at end of file diff --git a/user/themes/test/scss/spectre/utilities/_text.scss b/user/themes/test/scss/spectre/utilities/_text.scss new file mode 100755 index 0000000..67793ac --- /dev/null +++ b/user/themes/test/scss/spectre/utilities/_text.scss @@ -0,0 +1,64 @@ +// Text +// Text alignment utilities +.text-left { + text-align: left; +} + +.text-right { + text-align: right; +} + +.text-center { + text-align: center; +} + +.text-justify { + text-align: justify; +} + +// Text transform utilities +.text-lowercase { + text-transform: lowercase; +} + +.text-uppercase { + text-transform: uppercase; +} + +.text-capitalize { + text-transform: capitalize; +} + +// Text style utilities +.text-normal { + font-weight: normal; +} + +.text-bold { + font-weight: bold; +} + +.text-italic { + font-style: italic; +} + +.text-large { + font-size: 1.2em; +} + +// Text overflow utilities +.text-ellipsis { + @include text-ellipsis(); +} + +.text-clip { + overflow: hidden; + text-overflow: clip; + white-space: nowrap; +} + +.text-break { + hyphens: auto; + word-break: break-word; + word-wrap: break-word; +} diff --git a/user/themes/test/scss/theme.scss b/user/themes/test/scss/theme.scss new file mode 100644 index 0000000..9f17a70 --- /dev/null +++ b/user/themes/test/scss/theme.scss @@ -0,0 +1,21 @@ +// Core variables and mixins +@import 'theme/variables'; +@import 'spectre/variables'; +@import 'spectre/mixins'; + +@import 'theme/fonts'; +@import 'theme/mixins'; +@import 'theme/framework'; +@import 'theme/typography'; +@import 'theme/forms'; +@import 'theme/mobile'; +@import 'theme/animation'; + +@import 'theme/header'; +@import 'theme/footer'; +@import 'theme/menu'; + +// Extra Skeleton Styling +@import 'theme/blog'; +@import 'theme/onepage'; + diff --git a/user/themes/test/scss/theme/_animation.scss b/user/themes/test/scss/theme/_animation.scss new file mode 100644 index 0000000..3809282 --- /dev/null +++ b/user/themes/test/scss/theme/_animation.scss @@ -0,0 +1,23 @@ +.default-animation { + transition: all 0.5s ease; +} + +// Pulse Animation +.pulse { + animation-name: pulse_animation; + animation-duration: 2000ms; + transform-origin:70% 70%; + animation-iteration-count: infinite; + animation-timing-function: linear; +} + +@keyframes pulse_animation { + 0% { transform: scale(1); } + 30% { transform: scale(1); } + 40% { transform: scale(1.08); } + 50% { transform: scale(1); } + 60% { transform: scale(1); } + 70% { transform: scale(1.05); } + 80% { transform: scale(1); } + 100% { transform: scale(1); } +} diff --git a/user/themes/test/scss/theme/_blog.scss b/user/themes/test/scss/theme/_blog.scss new file mode 100644 index 0000000..3c7cd91 --- /dev/null +++ b/user/themes/test/scss/theme/_blog.scss @@ -0,0 +1,114 @@ +/** Extra columns spacing **/ +.extra-spacing:not(.col-12), :not(.col12) > .e-content { + padding-right: 1rem; + + @include breakpoint(md) { + padding-right: 0; + } +} + +/** Breadcrumbs styling **/ +#breadcrumbs { + padding-left: 0; + display: flex; + align-items: center; + margin-top: -1rem; + margin-bottom: 1rem; + + + i { + display: none; + } + + span, a { + padding: 0 0.5rem; + &:first-child { + padding-left: 0; + } + } + + span, a { + &:not(:first-child)::before { + color: #e7e9ed; + content: "/"; + padding-right: 1rem; + } + } +} + +/** Blog Listing **/ +.blog-listing { + + .bricklayer-column { + padding-left: 0px; + padding-right: 25px; + + } + .card { + margin-bottom: 25px; + border: 0; + box-shadow: 0 10px 45px -9px rgba(0,0,0,0.1); + } + + .card-footer { + text-align: right; + } + + .blog-date { + font-size: 13px; + } + + .label { + + } +} + +/** Blog Item **/ +.content-title { + margin-bottom: 2rem; + + h2 { + margin-bottom: 0.5rem; + } +} + +.label { + font-size: 12px; + text-transform: uppercase; +} + +/** Pagination **/ +ul.pagination { + justify-content: center; +} + +.prev-next { + margin-top: 4rem; +} + +/** Sidebar specific tweaks **/ +#sidebar { + + ul.related-pages { + box-shadow: none; + padding: 0; + z-index: 1; + + li { + border-bottom: 1px solid $border-color; + &:last-child { + border-bottom: 0; + } + } + + } + + ul.archives { + list-style: none; + margin-left: 0; + + .label { + vertical-align: text-top; + } + } +} diff --git a/user/themes/test/scss/theme/_extensions.scss b/user/themes/test/scss/theme/_extensions.scss new file mode 100644 index 0000000..975daa2 --- /dev/null +++ b/user/themes/test/scss/theme/_extensions.scss @@ -0,0 +1,7 @@ +.search-input, [data-grav-field="array"] input, [data-grav-field="array"] textarea { + @extend .form-input; +} + +.button { + @extend .btn; +} diff --git a/user/themes/test/scss/theme/_fonts.scss b/user/themes/test/scss/theme/_fonts.scss new file mode 100644 index 0000000..8809dc7 --- /dev/null +++ b/user/themes/test/scss/theme/_fonts.scss @@ -0,0 +1 @@ +$title-font-family: $base-font-family, $fallback-font-family !default; \ No newline at end of file diff --git a/user/themes/test/scss/theme/_footer.scss b/user/themes/test/scss/theme/_footer.scss new file mode 100644 index 0000000..e88afc5 --- /dev/null +++ b/user/themes/test/scss/theme/_footer.scss @@ -0,0 +1,17 @@ +// Sticky Footer solution +body.sticky-footer { + height: 100%; + min-height: 100vh; + display: flex; + flex-direction: column; + + #page-wrapper { + flex: 1 0 auto; + } +} + +#footer { + color: #acb3c2; + padding: 1rem $horiz-padding 0; + text-align: center; +} \ No newline at end of file diff --git a/user/themes/test/scss/theme/_forms.scss b/user/themes/test/scss/theme/_forms.scss new file mode 100644 index 0000000..f9a357f --- /dev/null +++ b/user/themes/test/scss/theme/_forms.scss @@ -0,0 +1,77 @@ +form { + .button-wrapper { + margin-top: 0.75rem; + margin-bottom: 1rem; + } + + span.required { + color: $error-color; + font-weight: 700; + font-size: 1.2rem; + } + + .form-input[type=range] { + appearance: slider-horizontal; + &:focus { + box-shadow: none; + border: none; + } + } + + /** Reset some defaults for Quark Theme **/ + .form-group:not(.form-field-toggleable) { + .checkboxes { + display: inherit; + + label { + display: inherit; + padding: (($control-size-sm - $line-height) / 2) $control-padding-x (($control-size-sm - $line-height) / 2) ($control-icon-size + $control-padding-x); + margin: inherit; + + &:before { + display: none; + } + } + } + } + +} + +#grav-login { + + > form { + margin: 2rem auto 0; + max-width: 350px; + } + .form-label { + display: none; + } + .form-data { + margin: 1rem 0; + } + .form-input { + text-align: center; + } + .button-wrapper { + text-align: right; + + .form-data.rememberme { + margin: 0; + float: left; + } + } + + .login-form { + button[type="submit"] { + @include button-primary; + } + } + + .twofa-form { + button[type="submit"]:first-child { + @include button-primary; + float: right; + margin-left: 4px; + } + } +} diff --git a/user/themes/test/scss/theme/_framework.scss b/user/themes/test/scss/theme/_framework.scss new file mode 100644 index 0000000..7f39cba --- /dev/null +++ b/user/themes/test/scss/theme/_framework.scss @@ -0,0 +1,156 @@ +html { + height: 100%; +} + +#body-wrapper { + .container { + padding: $vert-padding; + } + + // Fixed Header solution + .header-fixed & { + padding-top: $header-height-large; + } +} + +.header-fixed { + .hero + #start > #body-wrapper { + padding-top: 0; + } +} + +section.section { + padding-left: $horiz-padding; + padding-right: $horiz-padding; + position: relative; +} + +.overlay-light, .overlay-dark, .overlay-light-gradient, .overlay-dark-gradient { + z-index: 0; +} + +// Hero +.hero { + display: flex; + align-items: center; + justify-content: center; + + padding-top: 6rem; + padding-bottom: 7rem; + background-size: cover; + background-position: center; + + h1 { + color: $header-text-dark; + font-size: 4rem; + } + + h2 { + color: rgba($header-text-dark, 0.8); + font-size: 2.5rem; + } + + &.hero-fullscreen { + min-height: 100vh; + } + + &.hero-large { + min-height: 500px; + } + + &.hero-medium { + min-height: 400px; + } + + &.hero-small { + min-height: 110px; + } + + &.hero-tiny { + min-height: 8rem; + } + + .header-fixed & { + background-position: 50% 0; + } + + //&.parallax { + // background-attachment: fixed; + //} + + @include breakpoint(md) { + h1 { + font-size: 3rem; + } + h2 { + font-size: 1.75rem; + } + } + + @include breakpoint(sm) { + h1 { + font-size: 2rem; + } + h2 { + font-size: 1.25rem; + } + } + + + &.text-light { + h1 { + color: $header-text-light; + } + h2 { + color: rgba($header-text-light, 0.8); + } + } + + p { + font-size: .9rem; + font-weight: 300; + } + + #to-start { + display: inline-block; + position: absolute; + bottom: 10px; + font-size: 2rem; + cursor: pointer; + } +} + +// Overlay +.image-overlay { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: -1; + + .overlay-light & { + background: rgba(#fff, 0.4); + } + + .overlay-light-gradient & { + background: linear-gradient(to bottom, rgba(#fff,0.5), rgba(#fff,0.2)); + } + + .overlay-dark & { + background: rgba(#000, 0.4); + } + + .overlay-dark-gradient & { + background: linear-gradient(to bottom, rgba(#000,0.5), rgba(#000,0.2)); + } +} + + + + + + + + + diff --git a/user/themes/test/scss/theme/_header.scss b/user/themes/test/scss/theme/_header.scss new file mode 100644 index 0000000..6bdddba --- /dev/null +++ b/user/themes/test/scss/theme/_header.scss @@ -0,0 +1,101 @@ +#header { + width: 100%; + height: $header-height-large; + border-bottom: 1px solid rgba($gray-color, 0.2); + @extend .default-animation; + + font-size: 0.7rem; + font-weight: 700; + + background: $light-color; + color: $dark-color; + + a { + color: $dark-color; + } + + .logo svg path { + fill: $header-color-dark; + } + + .header-dark &:not(.scrolled) { + background: $header-color-dark; + color: $light-color; + a { + color: rgba($light-color, 0.7) !important; + } + a.active { + color: $light-color !important; + } + .dropmenu ul ul a { + color: $dark-color !important; + } + .logo svg path { + fill: $light-color; + } + } + + .header-dark.header-transparent &:not(.scrolled) { + background: rgba(#000, 0.05); + } + + .header-transparent &:not(.scrolled) { + background: rgba(#fff, 0.05); + //border-bottom: 0 !important; + } + + .navbar-section { + height: $header-height-large; + @extend .default-animation; + + @include breakpoint(md) { + margin-right: 2rem; + } + } + + .navbar-section.desktop-menu { + @include breakpoint(md) { + display: none; + } + } + + .logo { + svg, img { + height: 42px; + display: inherit; + @extend .default-animation; + } + } + + // Fixed Header solution + .header-fixed & { + position: fixed; + top: 0; + z-index: 2; + } +} + +// Animate Fixed Header +body.header-fixed.header-animated { + #header.scrolled { + height: $header-height-small; + + .navbar-section { + height: $header-height-small; + } + + .logo { + svg, img { + height: 28px; + } + } + + ~ .mobile-menu .button_container { + top: 0.5rem; + } + } +} + +.login-status-wrapper { + white-space: nowrap; +} diff --git a/user/themes/test/scss/theme/_menu.scss b/user/themes/test/scss/theme/_menu.scss new file mode 100644 index 0000000..fee9483 --- /dev/null +++ b/user/themes/test/scss/theme/_menu.scss @@ -0,0 +1,94 @@ +.dropmenu { + + @include breakpoint(md) { + display: none; + } + + ul { + white-space: nowrap; + margin: 0; + display: flex; + + li { + position: relative; + margin: 0; + + a { + text-decoration: none; + padding: $dropmenu-vert-padding ($dropmenu-horiz-padding + $dropmenu-child-padding) $dropmenu-vert-padding $dropmenu-horiz-padding; + display: block; + + &:hover, &:focus, &.active { + color: $dropmenu-hover-text !important; + } + + &:before { + content: '\f107'; + font-family: 'FontAwesome'; + display: inline-block; + vertical-align: middle; + float: right; + margin-right: - (2 *$dropmenu-child-padding); + } + + &:only-child { + //background: none; + padding-right: $dropmenu-horiz-padding; + + &:before { + content: ''; + } + } + } + + &:hover { + & > ul { + display: block; + visibility: visible; + } + } + } + + ul li a:before { + content: '\f105'; + } + + ul { + position: absolute; + top: 100%; + list-style: none; + background: $dropmenu-bg; + box-shadow: $dropmenu-shadow; + visibility: hidden; + + ul { + position: absolute; + left: 100%; + top: 0; + } + } + + } + + & > ul > li { + display: inline-block; + } + + // Animation options + &.animated { + ul li { + transition: background .7s, color 0.5s; + } + + ul li:hover > ul { + opacity: 1; + transform: translateY(0); + } + + ul ul { + transition: transform .3s, opacity .5s; + opacity: 0; + transform: translateY(-10px); + } + } +} \ No newline at end of file diff --git a/user/themes/test/scss/theme/_mixins.scss b/user/themes/test/scss/theme/_mixins.scss new file mode 100644 index 0000000..273a0a4 --- /dev/null +++ b/user/themes/test/scss/theme/_mixins.scss @@ -0,0 +1,77 @@ +@mixin breakpoint($point) { + @if $point == 2x { + @media (min-width:$size-2x) { + @content; + } + } @else if $point == xl { + @media (max-width: $size-xl) { + @content; + } + + } @else if $point == lg { + @media (max-width: $size-lg) { + @content; + } + } @else if $point == md { + @media (max-width: $size-md) { + @content; + } + } @else if $point == sm { + @media (max-width: $size-sm) { + @content; + } + } @else if $point == xs { + @media (max-width: $size-xs) { + @content; + } + } @else { + @warn "Breakpoint mixin supports: xs, sm, md, lg, xl, 2x"; + } +} + +@mixin vertical-align($position: relative) { + position: $position; + top: 50%; + transform: translateY(-50%); +} + +@mixin button-primary() { + background: $primary-color; + border-color: $primary-color-dark; + color: $light-color; + &:focus, + &:hover { + background: darken($primary-color-dark, 2%); + border-color: darken($primary-color-dark, 5%); + color: $light-color; + } + &:active, + &.active { + background: darken($primary-color-dark, 4%); + border-color: darken($primary-color-dark, 7%); + color: $light-color; + } +} + +@function strip-unit($value) { + @return $value / ($value * 0 + 1); +} + +@mixin fluid-type($min-vw, $max-vw, $min-font-size, $max-font-size) { + $u1: unit($min-vw); + $u2: unit($max-vw); + $u3: unit($min-font-size); + $u4: unit($max-font-size); + + @if $u1 == $u2 and $u1 == $u3 and $u1 == $u4 { + & { + font-size: $min-font-size; + @media screen and (min-width: $min-vw) { + font-size: calc(#{$min-font-size} + #{strip-unit($max-font-size - $min-font-size)} * ((100vw - #{$min-vw}) / #{strip-unit($max-vw - $min-vw)})); + } + @media screen and (min-width: $max-vw) { + font-size: $max-font-size; + } + } + } +} diff --git a/user/themes/test/scss/theme/_mobile.scss b/user/themes/test/scss/theme/_mobile.scss new file mode 100644 index 0000000..c27b28f --- /dev/null +++ b/user/themes/test/scss/theme/_mobile.scss @@ -0,0 +1,193 @@ +.mobile-container { + position: absolute; + //width: 100%; + //height: 100%; + top: 40%; + left: 0; + margin: 0 auto; + z-index: 2; +} + +.mobile-logo { + svg, img { + height: 42px; + margin-top: .7rem; + margin-left: 1.4rem; + + path { + fill: $light-color; + } + + } +} + +// Hamburger Menu +.mobile-menu { + + display: none; + top: 0; + right: 0; + z-index: 3; + + .header-fixed & { + position: fixed; + } + + @include breakpoint(md) { + display: block; + } + + .button_container { + position: absolute; + top: 1.3rem; + right: $horiz-padding; + height: $mobile-button-height; + width: $mobile-button-width; + cursor: pointer; + z-index: 100; + transition: opacity .25s ease, top 0.5s ease; + + $bar-offset: $mobile-button-height / 3; + + &:hover { + opacity: .7; + } + + &.active { + position: fixed; + + .top { + transform: translateY($bar-offset) translateX(0) rotate(45deg); + background: $mobile-color-active; + } + .middle { + opacity: 0; + background: $mobile-color-active; + } + + .bottom { + transform: translateY(-($bar-offset)) translateX(0) rotate(-45deg); + background: $mobile-color-active; + } + } + + span { + background: $mobile-color-main; + border: none; + height: 4px; + width: 100%; + position: absolute; + top: 0; + left: 0; + transition: all .35s ease; + cursor: pointer; + + &:nth-of-type(2) { + top: $bar-offset; + } + + &:nth-of-type(3) { + top: $bar-offset * 2; + } + } + } +} + +.overlay { + position: fixed; + background: #000; + top: 0; + left: 0; + width: 100%; + height: 0%; + opacity: 0; + visibility: hidden; + transition: opacity .35s, visibility .35s, height .35s; + + &.open { + opacity: .95; + visibility: visible; + height: 100%; + + } + nav { + + position: relative; + margin: 0 auto; + text-align: center; + } +} + +.overlay-menu { + height: calc(100% - 90px); + overflow-y: scroll; + + & > .tree { + text-align: left; + } +} + + +.treemenu { + + &.treemenu-root { + margin: 1rem; + } + + li { + list-style: none; + margin: 0 0 1px; + padding: 5px 0; + line-height: 1.2rem; + + background: rgba($gray-color-dark,0.1); + + a { + display: block; + margin-left: 1.2rem; + font-size: 1rem; + + &:hover, &:focus, &.active { + color: $primary-color-light !important; + text-decoration: none; + } + } + } + + ul { + margin: 0 0 0 1rem; + } + + .toggler { + cursor: pointer; + vertical-align: top; + font-size: 1.1rem; + line-height: 1rem; + padding-left: 5px; + float: left; + + &:before { + display: inline-block; margin-right: 2pt; + } + } + + li.tree-empty > .toggler { + opacity: 0.3; cursor: default; + + &:before { + content: "\2022"; + } + } + + li.tree-closed > .toggler:before { + content: "+"; + } + + li.tree-opened > .toggler:before { + content: "\2212"; + } +} + +.mobile-nav-open { + overflow-y: hidden; +} diff --git a/user/themes/test/scss/theme/_onepage.scss b/user/themes/test/scss/theme/_onepage.scss new file mode 100644 index 0000000..00d8700 --- /dev/null +++ b/user/themes/test/scss/theme/_onepage.scss @@ -0,0 +1,122 @@ +.modular-hero { + #to-start { + bottom: 3.5rem; + } +} + +.modular-features { + text-align: center; + + &.offset-box { + .frame-box { + margin: -3rem (-1rem - $layout-spacing) 3rem; + padding: 1rem 1rem; + background: $light-color; + box-shadow: 0 0 75px 0 rgba($dark-color, 0.1); + } + } + + &.small { + + .columns { + margin-top: -1rem; + } + + .column:hover { + .feature-icon i { + color: $primary-color; + } + } + + .feature-icon { + display: block; + justify-content: left; + + + i { + position: relative; + display: inherit; + font-size: 70px; + margin: 0 auto 1rem; + transform: none; + left: auto; + top: auto; + color: $gray-color; + @extend .default-animation; + + } + h6 { + text-transform: none; + } + } + } + + .frame-box { + padding: 3rem 0; + + > p { + max-width: 600px; + margin-left: auto; + margin-right: auto; + } + } + + .column { + padding: 1rem; + + &:hover { + .feature-icon { + color: $gray-color; + h6 { + color: $primary-color; + } + } + .feature-content { + color: $gray-color-dark; + } + } + } + + .feature-icon { + font-size: 130px; + height: 100px; + color: $gray-color-light; + display: flex; + align-items: center; + justify-content: center; + position: relative; + margin: 1rem 0; + @extend .default-animation; + + i { + position: absolute; + left: 50%; + top: 50%; + transform: translateX(-50%) translateY(-50%); + } + + h6 { + background: $light-color; + line-height: 1; + z-index: 1; + text-transform: uppercase; + font-weight: 600; + margin: 0; + display: block; + color: $gray-color-dark; + } + } + + .feature-content { + color: $gray-color; + } +} + +.modular-text { + padding-top: 4rem; + padding-bottom: 4rem; + + .columns.left { + flex-direction: row-reverse; + } +} \ No newline at end of file diff --git a/user/themes/test/scss/theme/_typography.scss b/user/themes/test/scss/theme/_typography.scss new file mode 100644 index 0000000..e9e641f --- /dev/null +++ b/user/themes/test/scss/theme/_typography.scss @@ -0,0 +1,178 @@ +html { + @include fluid-type($size-xs, $size-xl, $min-responsive-font-size, $html-font-size); +} + +// Header Overrides +h1, h2, h3, h4, h5, h6 { + margin-top: 2rem; + font-family: $title-font-family; + color: darken($body-font-color, 10%); +} + +h1, .h1 { + font-size: 3rem; +} + +h2, .h2 { + font-size: 1.8rem; +} + +h6, .h6 { + font-weight: 400; +} + +.title-center { + h1, h2 { + text-align: center; + } +} + +.title-h1h2 { + h1 { + font-weight: 100; + margin-bottom: 0; + line-height: 1.1; + + strong, bold { + font-weight: 400; + } + } + h1 + h2 { + line-height: 1.1; + margin-top: 0; + } + +} + +// Typography Hints +.title-h1h2, .title-center { + h1 + h2 { + margin-bottom: 50px; + font-weight: 700; + } +} + +a:focus { + outline: none !important; + box-shadow: none !important; +} + +img { + max-width: 100%; +} + +// Tables +.table > table { + border-spacing: 0; + border-collapse: collapse; + width: 100%; +} + + +// Codeblocks +pre code, pre.xdebug-var-dump{ + background: #fafafa; + display: block; + padding: 1rem !important; + line-height: 1.5; + color: inherit; + border-radius: 2px; + overflow-x: auto; +} + +pre[class*="language-"] { + code { + border-radius: inherit; + padding: 0 !important; + overflow-x: initial; + } +} + +pre { + code:not(.hljs):not([class*="language-"]) { + background: #f8f8f8; + } +} + +// Icon Tweaks +i.fa { + + + &.fa-heart, &.fa-heart-o { + &.pulse { + color: #920 + } + } +} + +// Font Weights +b, +strong { + font-weight: 700; +} + +.heavy { + font-weight: 700; +} + +.light { + font-weight: 200; +} + +// Colors +.text-light { + color: rgba($light-color, 0.8); + + h1, h2, h3, h4, h5, h6 { + color: rgba($light-color, 0.9); + } +} + +// Error configuration +#error { + text-align: center; + position: relative; + margin-top: 5rem; + + .icon { + font-size: 50px; + } +} + +// Messages +#messages { + margin-bottom: 1rem; + + .icon { + font-size: 1rem; + } +} + +// Lists +ul, +ol { + margin-left: $unit-8; + + ul, + ol { + margin-left: $unit-8; + } +} + +ul { + list-style: disc outside; +} + +ol { + list-style: decimal outside; +} + +// Notices +.notices { + margin: 1.5rem 0; + p { + margin: 1rem 0; + } +} + + diff --git a/user/themes/test/scss/theme/_variables.scss b/user/themes/test/scss/theme/_variables.scss new file mode 100644 index 0000000..7ad9273 --- /dev/null +++ b/user/themes/test/scss/theme/_variables.scss @@ -0,0 +1,38 @@ +// Spectre Overrides +$primary-color: #3085EE !default; +$dark-color: #454d5d !default; +$light-color: #fff !default; +$gray-color: lighten($dark-color, 40%) !default; +$gray-color-dark: darken($gray-color, 25%) !default; +$border-color: lighten($dark-color, 60%) !default; +$bg-color: lighten($dark-color, 66%) !default; +$body-font-color: lighten($dark-color, 5%) !default; + +// Layout +$horiz-padding: 1rem; +$vert-padding: 2rem 0 2rem; + +// Fonts +$min-responsive-font-size: 16px; + +// Header +$header-height-large: 4rem; +$header-height-small: 2.3rem; +$header-color-dark: #222; +$header-text-light: $light-color; +$header-text-dark: darken($dark-color, 15%); + +// Dropdown Menu +$dropmenu-bg: $light-color; +$dropmenu-hover-text: $primary-color; +$dropmenu-horiz-padding: 20px; +$dropmenu-vert-padding: 7px; +$dropmenu-child-padding: 10px; +$dropmenu-shadow: 0 3px 5px rgba(0, 0, 0, 0.1); + +// Mobile Menu +$mobile-color-main: $primary-color; +$mobile-color-active: #FFF; +$mobile-color-link: #FFF; +$mobile-button-height: 24px; +$mobile-button-width: 28px; \ No newline at end of file diff --git a/user/themes/test/templates/blocks/base.html.twig b/user/themes/test/templates/blocks/base.html.twig new file mode 100644 index 0000000..a27a375 --- /dev/null +++ b/user/themes/test/templates/blocks/base.html.twig @@ -0,0 +1,3 @@ +{% block content_surround %} +{% block content %}{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/user/themes/test/templates/blog.html.twig b/user/themes/test/templates/blog.html.twig new file mode 100644 index 0000000..cd4f1b0 --- /dev/null +++ b/user/themes/test/templates/blog.html.twig @@ -0,0 +1,63 @@ +{% extends 'partials/base.html.twig' %} +{% set blog_image = page.media.images[page.header.hero_image] ?: page.media.images|first %} +{% set collection = page.collection() %} +{% set blog = page.find(header_var('blog_url')|defined(theme_var('blog-page'))) %} +{% set show_breadcrumbs = header_var('show_breadcrumbs', [page, blog])|defined(true) %} +{% set show_sidebar = header_var('show_sidebar', [page, blog])|defined(true) %} +{% set show_pagination = header_var('show_pagination', [page, blog])|defined(true) %} + +{% block stylesheets %} + {% do assets.addCss('theme://css/bricklayer.css') %} + {{ parent() }} +{% endblock %} + +{% block javascripts %} + {{ parent() }} + {% do assets.add('theme://js/bricklayer.min.js') %} + {% do assets.add('theme://js/scopedQuerySelectorShim.min.js') %} +{% endblock %} + + +{% block hero %} + {% include 'partials/hero.html.twig' with {id: 'blog-hero', content: page.content, hero_image: blog_image} %} +{% endblock %} + +{% block body %} +

    +
    + + {% if show_breadcrumbs and config.plugins.breadcrumbs.enabled %} + {% include 'partials/breadcrumbs.html.twig' %} + {% endif %} + + {% embed 'partials/layout.html.twig' with {blog: page} %} + {% block item %} + +
    + {% for child in collection %} + {% include 'partials/blog-list-item.html.twig' with {blog: page, page: child} %} + {% endfor %} +
    + + {% if show_pagination and config.plugins.pagination.enabled and collection.params.pagination %} + + {% endif %} + + {% endblock %} + + {% block sidebar %} + {% include 'partials/sidebar.html.twig' %} + {% endblock %} + {% endembed %} +
    +
    + +{% endblock %} + + + diff --git a/user/themes/test/templates/comments.html.twig b/user/themes/test/templates/comments.html.twig new file mode 100644 index 0000000..0197666 --- /dev/null +++ b/user/themes/test/templates/comments.html.twig @@ -0,0 +1,7 @@ +{% extends 'partials/base.html.twig' %} + +{% block content %} + {{ page.content|raw }} + + {{ comments_section() }} +{% endblock %} \ No newline at end of file diff --git a/user/themes/test/templates/default.html.twig b/user/themes/test/templates/default.html.twig new file mode 100644 index 0000000..1e97738 --- /dev/null +++ b/user/themes/test/templates/default.html.twig @@ -0,0 +1,5 @@ +{% extends 'partials/base.html.twig' %} + +{% block content %} + {{ page.content|raw }} +{% endblock %} diff --git a/user/themes/test/templates/error.html.twig b/user/themes/test/templates/error.html.twig new file mode 100644 index 0000000..5ecf0ae --- /dev/null +++ b/user/themes/test/templates/error.html.twig @@ -0,0 +1,12 @@ +{% extends 'partials/base.html.twig' %} + +{% block content %} +
    +
    +

    {{ 'PLUGIN_ERROR.ERROR'|t }} {{ page.header.http_response_code }}

    +

    + {{ page.content|raw }} +

    +
    +
    +{% endblock %} diff --git a/user/themes/test/templates/forms/fields/checkbox/checkbox.html.twig b/user/themes/test/templates/forms/fields/checkbox/checkbox.html.twig new file mode 100644 index 0000000..ee60e71 --- /dev/null +++ b/user/themes/test/templates/forms/fields/checkbox/checkbox.html.twig @@ -0,0 +1,32 @@ +{% extends "forms/field.html.twig" %} + +{% block label %} +{% endblock %} + +{% block input %} + {% set id = field.id|default(field.name) ~ '-' ~ key %} +
    + +
    +{% endblock %} diff --git a/user/themes/test/templates/forms/fields/checkboxes/checkboxes.html.twig b/user/themes/test/templates/forms/fields/checkboxes/checkboxes.html.twig new file mode 100644 index 0000000..8674ab8 --- /dev/null +++ b/user/themes/test/templates/forms/fields/checkboxes/checkboxes.html.twig @@ -0,0 +1,44 @@ +{% extends "forms/field.html.twig" %} + +{% set originalValue = value %} +{% set value = (value is null ? field.default : value) %} +{% if field.use == 'keys' and field.default %} + {% set value = field.default|merge(value) %} +{% endif %} + +{% block global_attributes %} + {{ parent() }} + data-grav-keys="{{ field.use == 'keys' ? 'true' : 'false' }}" + data-grav-field-name="{{ (scope ~ field.name)|fieldName }}" +{% endblock %} + +{% block input %} + {% for key, text in field.options %} + + {% set id = field.id|default(field.name) ~ '-' ~ key %} + {% set name = field.use == 'keys' ? key : id %} + {% set val = field.use == 'keys' ? '1' : key %} + {% set checked = (field.use == 'keys' ? value[key] : key in value) %} + {% set help = (key in field.help_options|keys ? field.help_options[key] : false) %} + +
    + +
    + {% endfor %} +{% endblock %} diff --git a/user/themes/test/templates/forms/fields/radio/radio.html.twig b/user/themes/test/templates/forms/fields/radio/radio.html.twig new file mode 100644 index 0000000..ecda8f4 --- /dev/null +++ b/user/themes/test/templates/forms/fields/radio/radio.html.twig @@ -0,0 +1,26 @@ +{% extends "forms/field.html.twig" %} + +{% set originalValue = value %} +{% set value = (value is null ? field.default : value) %} + +{% block input %} + {% for key, text in field.options %} + {% set id = field.id|default(field.name) ~ '-' ~ key %} + +
    + +
    + {% endfor %} +{% endblock %} diff --git a/user/themes/test/templates/forms/fields/switch/switch.html.twig b/user/themes/test/templates/forms/fields/switch/switch.html.twig new file mode 100644 index 0000000..24d5609 --- /dev/null +++ b/user/themes/test/templates/forms/fields/switch/switch.html.twig @@ -0,0 +1,3 @@ +{% set form_field_checkbox_classes = 'form-switch' %} +{% extends "forms/fields/checkbox/checkbox.html.twig" %} + diff --git a/user/themes/test/templates/item.html.twig b/user/themes/test/templates/item.html.twig new file mode 100644 index 0000000..f73fc52 --- /dev/null +++ b/user/themes/test/templates/item.html.twig @@ -0,0 +1,41 @@ +{% extends 'partials/base.html.twig' %} +{% set blog = page.find(header_var('blog_url')|defined(theme_var('blog-page'))) %} +{% set show_breadcrumbs = header_var('show_breadcrumbs', [page, blog])|defined(true) %} +{% set show_sidebar = header_var('show_sidebar', [page, blog])|defined(true) %} +{% set show_pagination = header_var('show_pagination', [page, blog])|defined(true) %} +{% set hero_image_name = page.header.hero_image %} + +{% block hero %} + {% if hero_image_name %} + {% set hero_image = page.media[hero_image_name] %} + {% set content %} +

    {{ page.title }}

    +

    {{ page.header.subtitle }}

    + {% include 'partials/blog/date.html.twig' %} + {% include 'partials/blog/taxonomy.html.twig' %} + {% endset %} + {% include 'partials/hero.html.twig' with {id: 'blog-hero'} %} + + {% endif %} +{% endblock %} + +{% block body %} +
    +
    + + {% if show_breadcrumbs and config.plugins.breadcrumbs.enabled %} + {% include 'partials/breadcrumbs.html.twig' %} + {% endif %} + + {% embed 'partials/layout.html.twig' %} + {% block item %} + {% include 'partials/blog-item.html.twig' %} + {% endblock %} + {% block sidebar %} + {% include 'partials/sidebar.html.twig' %} + {% endblock %} + {% endembed %} + +
    +
    +{% endblock %} diff --git a/user/themes/test/templates/macros/macros.html.twig b/user/themes/test/templates/macros/macros.html.twig new file mode 100644 index 0000000..d11a20a --- /dev/null +++ b/user/themes/test/templates/macros/macros.html.twig @@ -0,0 +1,16 @@ +{% macro nav_loop(page) %} + {% import _self as macros %} + {% for p in page.children.visible %} + {% set active_page = (p.active or p.activeChild) ? 'active' : '' %} +
  • + + {{ p.menu }} + + {% if p.children.visible.count > 0 %} +
      + {{ macros.nav_loop(p) }} +
    + {% endif %} +
  • + {% endfor %} +{% endmacro %} \ No newline at end of file diff --git a/user/themes/test/templates/modular.html.twig b/user/themes/test/templates/modular.html.twig new file mode 100644 index 0000000..dc6b175 --- /dev/null +++ b/user/themes/test/templates/modular.html.twig @@ -0,0 +1,60 @@ +{% extends 'partials/base.html.twig' %} + +{% set show_onpage_menu = header.onpage_menu == true or header.onpage_menu is null %} + +{% block javascripts %} + {% if show_onpage_menu %} + {% do assets.add('theme://js/singlepagenav.min.js') %} + {% endif %} + {{ parent() }} +{% endblock %} + +{% block bottom %} + {{ parent() }} + {% if show_onpage_menu %} + + {% endif %} +{% endblock %} + +{% block header_navigation %} + {% if show_onpage_menu %} + + {% else %} + {{ parent() }} + {% endif %} +{% endblock %} + +{% block hero %} + {% for module in page.collection() if module.template == 'modular/hero' %} +
    + {{ module.content|raw }} + {% endfor %} +{% endblock %} + +{% block body %} + {% for module in page.collection() if module.template != 'modular/hero' %} +
    + {{ module.content|raw }} + {% endfor %} +{% endblock %} diff --git a/user/themes/test/templates/modular/features.html.twig b/user/themes/test/templates/modular/features.html.twig new file mode 100644 index 0000000..46fe7f3 --- /dev/null +++ b/user/themes/test/templates/modular/features.html.twig @@ -0,0 +1,30 @@ +{% set grid_size = theme_var('grid-size') %} +{% set columns = page.header.class == 'small' ? 'col-3 col-md-4 col-sm-6' : 'col-4 col-md-6 col-sm-12' %} +
    +
    +
    + + {{ content|raw }} + +
    + {% for feature in page.header.features %} +
    + {% if feature.url %}{% endif %} +
    + + {% if feature.header %} +
    {{ feature.header }}
    + {% endif %} +
    + {% if feature.url %}
    {% endif %} +
    + {% if feature.text %} +

    {{ feature.text }}

    + {% endif %} +
    +
    + {% endfor %} +
    +
    +
    +
    diff --git a/user/themes/test/templates/modular/gallery.html.twig b/user/themes/test/templates/modular/gallery.html.twig new file mode 100644 index 0000000..66fdc96 --- /dev/null +++ b/user/themes/test/templates/modular/gallery.html.twig @@ -0,0 +1,83 @@ +{% set styling %} +.lightbox-gallery { + max-width: 1200px; + margin: 0 auto; + padding: 0 20px; +} + +.lightbox-gallery .lightbox-gallery__columns { + display: flex; + flex-wrap: wrap; + margin: 0 -10px; +} + +.lightbox-gallery .lightbox-gallery__column { + width: 50%; + padding: 0 10px; + margin-bottom: 20px; +} + +.lightbox-gallery img { + display: block; + border-radius: 5px; + transition: all .2s ease-in-out; +} + +.lightbox-gallery img:hover { + filter: brightness(60%); + transform: scale(1.05); +} + +@media only screen and (min-width: 600px) { + .lightbox-gallery .lightbox-gallery__column { + width: calc(100% / 3); + } +} + +@media only screen and (min-width: 1000px) { + .lightbox-gallery .lightbox-gallery__column { + width: 25%; + } +} + +.lightbox-gallery .hidden { + display: none; +} +{% endset %} + +{% do assets.addInlineCss(styling) %} + +{% block module_content %} + {% set data = page.header.gallery %} + {% set thumb_width = data.thumb.width|default(600) %} + {% set thumb_height = data.thumb.height|default(450) %} + + +{% endblock %} diff --git a/user/themes/test/templates/modular/hero.html.twig b/user/themes/test/templates/modular/hero.html.twig new file mode 100644 index 0000000..7dbba44 --- /dev/null +++ b/user/themes/test/templates/modular/hero.html.twig @@ -0,0 +1,4 @@ +{% set grid_size = theme_var('grid-size') %} +{% set hero_image = page.header.hero_image ? page.media[page.header.hero_image] : page.media.images|first %} + +{% include 'partials/hero.html.twig' %} diff --git a/user/themes/test/templates/modular/text.html.twig b/user/themes/test/templates/modular/text.html.twig new file mode 100644 index 0000000..4567a6f --- /dev/null +++ b/user/themes/test/templates/modular/text.html.twig @@ -0,0 +1,21 @@ +{% set grid_size = theme_var('grid-size') %} +{% set image = page.media.images|first %} + +
    +
    +
    + {% if image %} +
    + {{ content|raw }} +
    +
    + {{ image.html|raw }} +
    + {% else %} +
    + {{ content|raw }} +
    + {% endif %} +
    +
    +
    diff --git a/user/themes/test/templates/partials/archives.html.twig b/user/themes/test/templates/partials/archives.html.twig new file mode 100644 index 0000000..5c5a774 --- /dev/null +++ b/user/themes/test/templates/partials/archives.html.twig @@ -0,0 +1,13 @@ + diff --git a/user/themes/test/templates/partials/base.html.twig b/user/themes/test/templates/partials/base.html.twig new file mode 100644 index 0000000..f3ca337 --- /dev/null +++ b/user/themes/test/templates/partials/base.html.twig @@ -0,0 +1,112 @@ +{% set body_classes = body_class(['header-fixed', 'header-animated', 'header-dark', 'header-transparent', 'sticky-footer']) %} +{% set grid_size = theme_var('grid-size') %} +{% set compress = theme_var('production-mode') ? '.min.css' : '.css' %} +{% use 'blocks/base.html.twig' %} + + + +{% block head deferred %} + + {% if page.title %}{{ page.title|e('html') }} | {% endif %}{{ site.title|e('html') }} + + + + {% include 'partials/metadata.html.twig' %} + + + +{% endblock head %} + +{% block stylesheets %} + {% do assets.addCss('theme://css-compiled/spectre'~compress) %} + {% if theme_var('spectre.exp') %}{% do assets.addCss('theme://css-compiled/spectre-exp'~compress) %}{% endif %} + {% if theme_var('spectre.icons') %}{% do assets.addCss('theme://css-compiled/spectre-icons'~compress) %}{% endif %} + {% do assets.addCss('theme://css-compiled/theme'~compress) %} + {% do assets.addCss('theme://css/custom.css') %} + {% do assets.addCss('theme://css/line-awesome.min.css') %} +{% endblock %} + +{% block javascripts %} + {% do assets.addJs('jquery', 101) %} + {% do assets.addJs('theme://js/jquery.treemenu.js', {group:'bottom'}) %} + {% do assets.addJs('theme://js/site.js', {group:'bottom'}) %} + {% do assets.addJs('theme://js/perso.js', {group:'bottom'}) %} +{% endblock %} + +{% block assets deferred %} + {{ assets.css()|raw }} + {{ assets.js()|raw }} +{% endblock %} + + +
    + {% block header %} + {# +
    +
    + + + +
    +
    #} + {% endblock %} + + {% block hero %}{% endblock %} + +
    + {% block body %} +
    +
    + {% block messages %} + {% include 'partials/messages.html.twig' ignore missing %} + {% endblock %} + {{ block('content_surround') }} +
    +
    + {% endblock %} +
    + +
    + + + + {% block mobile %} +
    +
    + + +
    +
    + {% endblock %} + +{% block bottom %} + {{ assets.js('bottom')|raw }} +{% endblock %} + + + diff --git a/user/themes/test/templates/partials/blog-item.html.twig b/user/themes/test/templates/partials/blog-item.html.twig new file mode 100644 index 0000000..cdc74b8 --- /dev/null +++ b/user/themes/test/templates/partials/blog-item.html.twig @@ -0,0 +1,30 @@ +
    + +{% if not hero_image_name %} +
    + {% include 'partials/blog/title.html.twig' with {title_level: 'h2'} %} + {% if page.header.subtitle %} +

    {{ page.header.subtitle }}

    + {% endif %} + {% include 'partials/blog/date.html.twig' %} + {% include 'partials/blog/taxonomy.html.twig' %} +
    +{% endif %} +
    + {{ page.content|raw }} +
    + + {% if page.header.continue_link is same as(true) and config.plugins.comments.enabled %} + {% include 'partials/comments.html.twig' %} + {% endif %} +
    + +

    + {% if not page.isLast %} + {{ 'THEME_QUARK.BLOG.ITEM.PREV_POST'|t }} + {% endif %} + + {% if not page.isFirst %} + {{ 'THEME_QUARK.BLOG.ITEM.NEXT_POST'|t }} + {% endif %} +

    diff --git a/user/themes/test/templates/partials/blog-list-item.html.twig b/user/themes/test/templates/partials/blog-list-item.html.twig new file mode 100644 index 0000000..74d0ddf --- /dev/null +++ b/user/themes/test/templates/partials/blog-list-item.html.twig @@ -0,0 +1,27 @@ +
    + {% set image = page.media.images|first %} + {% if image %} + + {% endif %} +
    +
    + {% include 'partials/blog/date.html.twig' %} +
    +
    + {% include 'partials/blog/title.html.twig' with {title_level: 'h5'} %} +
    +
    +
    + {% if page.summary != page.content %} + {{ page.summary|raw }} + {% else %} + {{ page.content|raw }} + {% endif %} +
    + +
    + diff --git a/user/themes/test/templates/partials/blog/date.html.twig b/user/themes/test/templates/partials/blog/date.html.twig new file mode 100644 index 0000000..a134d24 --- /dev/null +++ b/user/themes/test/templates/partials/blog/date.html.twig @@ -0,0 +1,5 @@ + + + diff --git a/user/themes/test/templates/partials/blog/page-summary.html.twig b/user/themes/test/templates/partials/blog/page-summary.html.twig new file mode 100644 index 0000000..3c8fb7f --- /dev/null +++ b/user/themes/test/templates/partials/blog/page-summary.html.twig @@ -0,0 +1,8 @@ +
    + {% if page.summary != page.content %} + {{ page.summary|raw }} + {% else %} + {{ page.content|raw }} + {% endif %} +
    + diff --git a/user/themes/test/templates/partials/blog/taxonomy.html.twig b/user/themes/test/templates/partials/blog/taxonomy.html.twig new file mode 100644 index 0000000..8cab9fc --- /dev/null +++ b/user/themes/test/templates/partials/blog/taxonomy.html.twig @@ -0,0 +1,7 @@ +{% if page.taxonomy.tag %} + + {% for tag in page.taxonomy.tag %} + {{ tag }} + {% endfor %} + +{% endif %} diff --git a/user/themes/test/templates/partials/blog/title.html.twig b/user/themes/test/templates/partials/blog/title.html.twig new file mode 100644 index 0000000..0a235a1 --- /dev/null +++ b/user/themes/test/templates/partials/blog/title.html.twig @@ -0,0 +1,11 @@ +{% set title_level = title_level ?: 'h2' %} +{% if page.header.link %} + <{{ title_level }} class="p-name mt-1"> + {% if page.header.continue_link is not same as(false) %} + + {% endif %} + {{ page.title }} + +{% else %} + <{{ title_level }} class="p-name mt-1">{{ page.title }} +{% endif %} diff --git a/user/themes/test/templates/partials/footer.html.twig b/user/themes/test/templates/partials/footer.html.twig new file mode 100644 index 0000000..af2f8b5 --- /dev/null +++ b/user/themes/test/templates/partials/footer.html.twig @@ -0,0 +1,5 @@ + diff --git a/user/themes/test/templates/partials/form-messages.html.twig b/user/themes/test/templates/partials/form-messages.html.twig new file mode 100644 index 0000000..2dd7b91 --- /dev/null +++ b/user/themes/test/templates/partials/form-messages.html.twig @@ -0,0 +1,6 @@ +{% if form.message %} + {% set inline_errors = form.inline_errors is not null ? form.inline_errors : config.plugins.form.inline_errors(false) %} + {% set status_mapping = {'success':'green', 'error': 'red', 'warning': 'yellow'} %} + {% set message = inline_errors and form.messages ? "GRAV.FORM.VALIDATION_FAIL"|t : form.message %} +
    {{ message|raw }}
    +{% endif %} \ No newline at end of file diff --git a/user/themes/test/templates/partials/hero.html.twig b/user/themes/test/templates/partials/hero.html.twig new file mode 100644 index 0000000..527581f --- /dev/null +++ b/user/themes/test/templates/partials/hero.html.twig @@ -0,0 +1,7 @@ +
    +
    +
    + {{ content|raw }} +
    + +
    diff --git a/user/themes/test/templates/partials/layout.html.twig b/user/themes/test/templates/partials/layout.html.twig new file mode 100644 index 0000000..fdc7bed --- /dev/null +++ b/user/themes/test/templates/partials/layout.html.twig @@ -0,0 +1,14 @@ +{% set item_col = show_sidebar ? 'col-9 col-md-12' : 'col-12' %} +{% set sidebar_col = show_sidebar ? 'col-3 col-md-12' : 'col-12' %} + +
    +
    + {% block item %}{% endblock %} +
    + {% if show_sidebar %} + + {% endif %} +
    + diff --git a/user/themes/test/templates/partials/logo.html.twig b/user/themes/test/templates/partials/logo.html.twig new file mode 100644 index 0000000..658a6be --- /dev/null +++ b/user/themes/test/templates/partials/logo.html.twig @@ -0,0 +1,9 @@ +{% set logo = theme_var(mobile ? 'custom_logo_mobile' : 'custom_logo') %} + +{% if logo %} + {% set logo_file = (logo|first).name %} + {{ site.title }} +{% else %} + {% include('@images/grav-logo.svg') %} +{% endif %} + \ No newline at end of file diff --git a/user/themes/test/templates/partials/messages.html.twig b/user/themes/test/templates/partials/messages.html.twig new file mode 100644 index 0000000..662333d --- /dev/null +++ b/user/themes/test/templates/partials/messages.html.twig @@ -0,0 +1,17 @@ +{% set type_mapping = {'info':'success', 'error': 'error', 'warning': 'warning'} %} +{% set icon_mapping = {'info':'checkmark', 'error':'wrong', 'warning':'information'} %} + +{% if grav.messages.all %} +
    + {% for message in grav.messages.fetch %} + + {% set scope = message.scope|e %} + {% set type = type_mapping[scope] %} + {% set icon = icon_mapping[scope] %} + +
    + {{ message.message|raw }} +
    + {% endfor %} +
    +{% endif %} \ No newline at end of file diff --git a/user/themes/test/templates/partials/navigation.html.twig b/user/themes/test/templates/partials/navigation.html.twig new file mode 100644 index 0000000..9474b77 --- /dev/null +++ b/user/themes/test/templates/partials/navigation.html.twig @@ -0,0 +1,6 @@ +{% import 'macros/macros.html.twig' as macros %} + +
      + {{ macros.nav_loop(pages) }} +
    + diff --git a/user/themes/test/templates/partials/relatedpages.html.twig b/user/themes/test/templates/partials/relatedpages.html.twig new file mode 100644 index 0000000..99e6563 --- /dev/null +++ b/user/themes/test/templates/partials/relatedpages.html.twig @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/user/themes/test/templates/partials/sidebar.html.twig b/user/themes/test/templates/partials/sidebar.html.twig new file mode 100644 index 0000000..598d8b2 --- /dev/null +++ b/user/themes/test/templates/partials/sidebar.html.twig @@ -0,0 +1,43 @@ +{% set feed_url = blog.url == '/' or blog.url == base_url_relative ? (base_url_relative~'/'~blog.slug) : blog.url %} +{% set new_base_url = blog.url == '/' ? '' : blog.url %} + +{% if config.plugins.simplesearch.enabled %} + +{% endif %} +{% if config.plugins.relatedpages.enabled and related_pages|length > 0 %} + +{% endif %} +{% if config.plugins.random.enabled %} + +{% endif %} + +{{ page.find('/modules/sidebar').content|raw }} + +{% if config.plugins.taxonomylist.enabled %} + +{% endif %} +{% if config.plugins.archives.enabled %} + +{% endif %} +{% if config.plugins.feed.enabled %} + +{% endif %} diff --git a/user/themes/test/templates/partials/taxonomylist.html.twig b/user/themes/test/templates/partials/taxonomylist.html.twig new file mode 100644 index 0000000..a81d796 --- /dev/null +++ b/user/themes/test/templates/partials/taxonomylist.html.twig @@ -0,0 +1,10 @@ +{% set taxlist = children_only is defined ? taxonomylist.getChildPagesTags(of_page, children_only) : taxonomylist.get() %} + +{% if taxlist %} + + {% for tax,value in taxlist[taxonomy] %} + {% set label_class = uri.param(taxonomy) == tax ? 'label-primary' : 'label-secondary' %} + {{ tax }} + {% endfor %} + +{% endif %} diff --git a/user/themes/test/test.php b/user/themes/test/test.php new file mode 100644 index 0000000..b8adf95 --- /dev/null +++ b/user/themes/test/test.php @@ -0,0 +1,56 @@ + ['onThemeInitialized', 0], + 'onTwigLoader' => ['onTwigLoader', 0], + 'onTwigInitialized' => ['onTwigInitialized', 0], + ]; + } + + public function onThemeInitialized() + { + + } + + // Add images to twig template paths to allow inclusion of SVG files + public function onTwigLoader() + { + $theme_paths = Grav::instance()['locator']->findResources('theme://images'); + foreach($theme_paths as $images_path) { + $this->grav['twig']->addPath($images_path, 'images'); + } + } + + public function onTwigInitialized() + { + $twig = $this->grav['twig']; + + $form_class_variables = [ +// 'form_outer_classes' => 'form-horizontal', + 'form_button_outer_classes' => 'button-wrapper', + 'form_button_classes' => 'btn', + 'form_errors_classes' => '', + 'form_field_outer_classes' => 'form-group', + 'form_field_outer_label_classes' => 'form-label-wrapper', + 'form_field_label_classes' => 'form-label', +// 'form_field_outer_data_classes' => 'col-9', + 'form_field_input_classes' => 'form-input', + 'form_field_textarea_classes' => 'form-input', + 'form_field_select_classes' => 'form-select', + 'form_field_radio_classes' => 'form-radio', + 'form_field_checkbox_classes' => 'form-checkbox', + ]; + + $twig->twig_vars = array_merge($twig->twig_vars, $form_class_variables); + + } + +} \ No newline at end of file diff --git a/user/themes/test/test.yaml b/user/themes/test/test.yaml new file mode 100644 index 0000000..604df26 --- /dev/null +++ b/user/themes/test/test.yaml @@ -0,0 +1,12 @@ +enabled: true +production-mode: true +grid-size: grid-lg +header-fixed: true +header-animated: true +header-dark: false +header-transparent: false +sticky-footer: true +blog-page: '/blog' +spectre: + exp: false + icons: false diff --git a/user/themes/test/thumbnail.jpg b/user/themes/test/thumbnail.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ce4e0ed3918a508cbe88609067c47ec5d9ea5317 GIT binary patch literal 49487 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 SPX;=w{uJo@djR)y_P+oN$I;{f literal 0 HcmV?d00001 diff --git a/user/themes/test/yarn.lock b/user/themes/test/yarn.lock new file mode 100644 index 0000000..4b14abe --- /dev/null +++ b/user/themes/test/yarn.lock @@ -0,0 +1,3680 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@gulp-sourcemaps/identity-map@1.X": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@gulp-sourcemaps/identity-map/-/identity-map-1.0.2.tgz#1e6fe5d8027b1f285dc0d31762f566bccd73d5a9" + integrity sha512-ciiioYMLdo16ShmfHBXJBOFm3xPC4AuwO4xeRpFeHz7WK9PYsWCmigagG2XyzZpubK4a3qNKoUBDhbzHfa50LQ== + dependencies: + acorn "^5.0.3" + css "^2.2.1" + normalize-path "^2.1.1" + source-map "^0.6.0" + through2 "^2.0.3" + +"@gulp-sourcemaps/map-sources@1.X": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz#890ae7c5d8c877f6d384860215ace9d7ec945bda" + integrity sha1-iQrnxdjId/bThIYCFazp1+yUW9o= + dependencies: + normalize-path "^2.0.1" + through2 "^2.0.3" + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +acorn@5.X, acorn@^5.0.3: + version "5.7.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" + integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== + +ajv@^6.5.5: + version "6.10.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" + integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw== + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +amdefine@>=0.0.4: + version "1.0.1" + resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" + integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU= + +ansi-colors@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-1.1.0.tgz#6374b4dd5d4718ff3ce27a671a3b1cad077132a9" + integrity sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA== + dependencies: + ansi-wrap "^0.1.0" + +ansi-gray@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-gray/-/ansi-gray-0.1.1.tgz#2962cf54ec9792c48510a3deb524436861ef7251" + integrity sha1-KWLPVOyXksSFEKPetSRDaGHvclE= + dependencies: + ansi-wrap "0.1.0" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-wrap@0.1.0, ansi-wrap@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" + integrity sha1-qCJQ3bABXponyoLoLqYDu/pF768= + +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +append-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/append-buffer/-/append-buffer-1.0.2.tgz#d8220cf466081525efea50614f3de6514dfa58f1" + integrity sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE= + dependencies: + buffer-equal "^1.0.0" + +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +archy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" + integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= + +are-we-there-yet@~1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + +arr-filter@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/arr-filter/-/arr-filter-1.1.2.tgz#43fdddd091e8ef11aa4c45d9cdc18e2dff1711ee" + integrity sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4= + dependencies: + make-iterator "^1.0.0" + +arr-flatten@^1.0.1, arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-map@^2.0.0, arr-map@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/arr-map/-/arr-map-2.0.2.tgz#3a77345ffc1cf35e2a91825601f9e58f2e24cac4" + integrity sha1-Onc0X/wc814qkYJWAfnljy4kysQ= + dependencies: + make-iterator "^1.0.0" + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + +array-differ@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" + integrity sha1-7/UuN1gknTO+QCuLuOVkuytdQDE= + +array-each@^1.0.0, array-each@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" + integrity sha1-p5SvDAWrF1KEbudTofIRoFugxE8= + +array-find-index@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= + +array-initial@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/array-initial/-/array-initial-1.1.0.tgz#2fa74b26739371c3947bd7a7adc73be334b3d795" + integrity sha1-L6dLJnOTccOUe9enrcc74zSz15U= + dependencies: + array-slice "^1.0.0" + is-number "^4.0.0" + +array-last@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/array-last/-/array-last-1.3.0.tgz#7aa77073fec565ddab2493f5f88185f404a9d336" + integrity sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg== + dependencies: + is-number "^4.0.0" + +array-slice@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.1.0.tgz#e368ea15f89bc7069f7ffb89aec3a6c7d4ac22d4" + integrity sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w== + +array-sort@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-sort/-/array-sort-1.0.0.tgz#e4c05356453f56f53512a7d1d6123f2c54c0a88a" + integrity sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg== + dependencies: + default-compare "^1.0.0" + get-value "^2.0.6" + kind-of "^5.0.2" + +array-uniq@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + +async-done@^1.2.0, async-done@^1.2.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/async-done/-/async-done-1.3.2.tgz#5e15aa729962a4b07414f528a88cdf18e0b290a2" + integrity sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.2" + process-nextick-args "^2.0.0" + stream-exhaust "^1.0.1" + +async-each@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" + integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== + +async-foreach@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" + integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= + +async-settle@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-settle/-/async-settle-1.0.0.tgz#1d0a914bb02575bec8a8f3a74e5080f72b2c0c6b" + integrity sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs= + dependencies: + async-done "^1.2.2" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +atob@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +autoprefixer@^9.5.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.1.tgz#51967a02d2d2300bb01866c1611ec8348d355a47" + integrity sha512-aVo5WxR3VyvyJxcJC3h4FKfwCQvQWb1tSI5VHNibddCVWrcD1NvlxEweg3TSgiPztMnWfjpy2FURKA2kvDE+Tw== + dependencies: + browserslist "^4.6.3" + caniuse-lite "^1.0.30000980" + chalk "^2.4.2" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^7.0.17" + postcss-value-parser "^4.0.0" + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" + integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== + +bach@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/bach/-/bach-1.2.0.tgz#4b3ce96bf27134f79a1b414a51c14e34c3bd9880" + integrity sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA= + dependencies: + arr-filter "^1.1.1" + arr-flatten "^1.0.1" + arr-map "^2.0.0" + array-each "^1.0.0" + array-initial "^1.0.0" + array-last "^1.1.1" + async-done "^1.2.2" + async-settle "^1.0.0" + now-and-later "^2.0.0" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +beeper@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809" + integrity sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak= + +binary-extensions@^1.0.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" + integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== + +block-stream@*: + version "0.0.9" + resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" + integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo= + dependencies: + inherits "~2.0.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^2.3.1, braces@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +browserslist@^4.6.3: + version "4.6.6" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.6.6.tgz#6e4bf467cde520bc9dbdf3747dafa03531cec453" + integrity sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA== + dependencies: + caniuse-lite "^1.0.30000984" + electron-to-chromium "^1.3.191" + node-releases "^1.1.25" + +buffer-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" + integrity sha1-WWFrSYME1Var1GaWayLu2j7KX74= + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +camelcase-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" + integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc= + dependencies: + camelcase "^2.0.0" + map-obj "^1.0.0" + +camelcase@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" + integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= + +camelcase@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" + integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= + +caniuse-lite@^1.0.30000980, caniuse-lite@^1.0.30000984: + version "1.0.30000989" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000989.tgz#b9193e293ccf7e4426c5245134b8f2a56c0ac4b9" + integrity sha512-vrMcvSuMz16YY6GSVZ0dWDTJP8jqk3iFQ/Aq5iqblPwxSVVZI+zxDyTX0VPqtQsDnfdrBDcsmhgTEOh5R8Lbpw== + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +chalk@^1.0.0, chalk@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^2.3.0, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chokidar@^2.0.0: + version "2.1.6" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.6.tgz#b6cad653a929e244ce8a834244164d241fa954c5" + integrity sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g== + dependencies: + anymatch "^2.0.0" + async-each "^1.0.1" + braces "^2.3.2" + glob-parent "^3.1.0" + inherits "^2.0.3" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + normalize-path "^3.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.2.1" + upath "^1.1.1" + optionalDependencies: + fsevents "^1.2.7" + +chownr@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.2.tgz#a18f1e0b269c8a6a5d3c86eb298beb14c3dd7bf6" + integrity sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A== + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +clean-css@4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.1.tgz#2d411ef76b8569b6d0c84068dabe85b0aa5e5c17" + integrity sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g== + dependencies: + source-map "~0.6.0" + +cliui@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi "^2.0.0" + +clone-buffer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" + integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg= + +clone-stats@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" + integrity sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE= + +clone-stats@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" + integrity sha1-s3gt/4u1R04Yuba/D9/ngvh3doA= + +clone@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= + +clone@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= + +cloneable-readable@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.1.3.tgz#120a00cb053bfb63a222e709f9683ea2e11d8cec" + integrity sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ== + dependencies: + inherits "^2.0.1" + process-nextick-args "^2.0.0" + readable-stream "^2.3.5" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + +collection-map@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-map/-/collection-map-1.0.0.tgz#aea0f06f8d26c780c2b75494385544b2255af18c" + integrity sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw= + dependencies: + arr-map "^2.0.2" + for-own "^1.0.0" + make-iterator "^1.0.0" + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-support@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.0.0.tgz#d1b86f901f8b64bd941bdeadaf924530393be928" + integrity sha1-0bhvkB+LZL2UG96tr5JFMDk76Sg= + +component-emitter@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +concat-stream@^1.6.0: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + +convert-source-map@1.X, convert-source-map@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" + integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A== + dependencies: + safe-buffer "~5.1.1" + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + +copy-props@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/copy-props/-/copy-props-2.0.4.tgz#93bb1cadfafd31da5bb8a9d4b41f471ec3a72dfe" + integrity sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A== + dependencies: + each-props "^1.3.0" + is-plain-object "^2.0.1" + +core-util-is@1.0.2, core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +cross-spawn@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" + integrity sha1-ElYDfsufDF9549bvE14wdwGEuYI= + dependencies: + lru-cache "^4.0.1" + which "^1.2.9" + +css@2.X, css@^2.2.1: + version "2.2.4" + resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" + integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw== + dependencies: + inherits "^2.0.3" + source-map "^0.6.1" + source-map-resolve "^0.5.2" + urix "^0.1.0" + +csscomb-core@3.0.0-3.1: + version "3.0.0-3.1" + resolved "https://registry.yarnpkg.com/csscomb-core/-/csscomb-core-3.0.0-3.1.tgz#b411c8d7cfe0df3f2fe1df84d1bd64a6f0046c68" + integrity sha1-tBHI18/g3z8v4d+E0b1kpvAEbGg= + dependencies: + gonzales-pe "3.0.0-28" + minimatch "0.2.12" + vow "0.4.4" + vow-fs "0.3.2" + +csscomb@^3.1.7: + version "3.1.8" + resolved "https://registry.yarnpkg.com/csscomb/-/csscomb-3.1.8.tgz#a8a738884f409baf35ec9461afc52e1c75bd23a2" + integrity sha1-qKc4iE9Am6817JRhr8UuHHW9I6I= + dependencies: + commander "2.0.0" + csscomb-core "3.0.0-3.1" + gonzales-pe "3.0.0-28" + vow "0.4.4" + +currently-unhandled@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" + integrity sha1-mI3zP+qxke95mmE2nddsF635V+o= + dependencies: + array-find-index "^1.0.1" + +d@1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +dateformat@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062" + integrity sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI= + +debug-fabulous@1.X: + version "1.1.0" + resolved "https://registry.yarnpkg.com/debug-fabulous/-/debug-fabulous-1.1.0.tgz#af8a08632465224ef4174a9f06308c3c2a1ebc8e" + integrity sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg== + dependencies: + debug "3.X" + memoizee "0.4.X" + object-assign "4.X" + +debug@3.X, debug@^3.2.6: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + +debug@^2.2.0, debug@^2.3.3: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +decamelize@^1.1.1, decamelize@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +default-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/default-compare/-/default-compare-1.0.0.tgz#cb61131844ad84d84788fb68fd01681ca7781a2f" + integrity sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ== + dependencies: + kind-of "^5.0.2" + +default-resolution@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/default-resolution/-/default-resolution-2.0.0.tgz#bcb82baa72ad79b426a76732f1a81ad6df26d684" + integrity sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ= + +define-properties@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + +detect-file@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" + integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= + +detect-libc@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + +detect-newline@2.X: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" + integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I= + +duplexer2@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" + integrity sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds= + dependencies: + readable-stream "~1.1.9" + +duplexify@^3.6.0: + version "3.7.1" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" + integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== + dependencies: + end-of-stream "^1.0.0" + inherits "^2.0.1" + readable-stream "^2.0.0" + stream-shift "^1.0.0" + +each-props@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/each-props/-/each-props-1.3.2.tgz#ea45a414d16dd5cfa419b1a81720d5ca06892333" + integrity sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA== + dependencies: + is-plain-object "^2.0.1" + object.defaults "^1.1.0" + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +electron-to-chromium@^1.3.191: + version "1.3.222" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.222.tgz#2a0e38903b2254d798dd8837507b5bc42c7e3934" + integrity sha512-Kv3rvtJELafNfgVBVNaDIdV0aWV7O1RlYqqAhg+s+OwpiXFYPsIvONYgAopmR/gpyxSYbHi0EKJmPOvaL7UzMg== + +end-of-stream@^1.0.0, end-of-stream@^1.1.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" + integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q== + dependencies: + once "^1.4.0" + +error-ex@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es5-ext@^0.10.35, es5-ext@^0.10.45, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: + version "0.10.50" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.50.tgz#6d0e23a0abdb27018e5ac4fd09b412bc5517a778" + integrity sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw== + dependencies: + es6-iterator "~2.0.3" + es6-symbol "~3.1.1" + next-tick "^1.0.0" + +es6-iterator@^2.0.1, es6-iterator@^2.0.3, es6-iterator@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-symbol@^3.1.1, es6-symbol@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" + integrity sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc= + dependencies: + d "1" + es5-ext "~0.10.14" + +es6-weak-map@^2.0.1, es6-weak-map@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53" + integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== + dependencies: + d "1" + es5-ext "^0.10.46" + es6-iterator "^2.0.3" + es6-symbol "^3.1.1" + +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +event-emitter@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" + integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk= + dependencies: + d "1" + es5-ext "~0.10.14" + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +expand-tilde@^2.0.0, expand-tilde@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" + integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI= + dependencies: + homedir-polyfill "^1.0.1" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extend@^3.0.0, extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +fancy-log@^1.1.0, fancy-log@^1.3.2: + version "1.3.3" + resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.3.tgz#dbc19154f558690150a23953a0adbd035be45fc7" + integrity sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw== + dependencies: + ansi-gray "^0.1.1" + color-support "^1.1.3" + parse-node-version "^1.0.0" + time-stamp "^1.0.0" + +fast-deep-equal@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= + +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + +findup-sync@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc" + integrity sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw= + dependencies: + detect-file "^1.0.0" + is-glob "^3.1.0" + micromatch "^3.0.4" + resolve-dir "^1.0.1" + +findup-sync@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" + integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== + dependencies: + detect-file "^1.0.0" + is-glob "^4.0.0" + micromatch "^3.0.4" + resolve-dir "^1.0.1" + +fined@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fined/-/fined-1.2.0.tgz#d00beccf1aa2b475d16d423b0238b713a2c4a37b" + integrity sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng== + dependencies: + expand-tilde "^2.0.2" + is-plain-object "^2.0.3" + object.defaults "^1.1.0" + object.pick "^1.2.0" + parse-filepath "^1.0.1" + +flagged-respawn@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.1.tgz#e7de6f1279ddd9ca9aac8a5971d618606b3aab41" + integrity sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q== + +flush-write-stream@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" + integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== + dependencies: + inherits "^2.0.3" + readable-stream "^2.3.6" + +for-in@^1.0.1, for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + +for-own@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" + integrity sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs= + dependencies: + for-in "^1.0.1" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + dependencies: + map-cache "^0.2.2" + +fs-minipass@^1.2.5: + version "1.2.6" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07" + integrity sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ== + dependencies: + minipass "^2.2.1" + +fs-mkdirp-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz#0b7815fc3201c6a69e14db98ce098c16935259eb" + integrity sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes= + dependencies: + graceful-fs "^4.1.11" + through2 "^2.0.3" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@^1.2.7: + version "1.2.9" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f" + integrity sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw== + dependencies: + nan "^2.12.1" + node-pre-gyp "^0.12.0" + +fstream@^1.0.0, fstream@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" + integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +gaze@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" + integrity sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g== + dependencies: + globule "^1.0.0" + +get-caller-file@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" + integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== + +get-stdin@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" + integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + +glob-stream@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-6.1.0.tgz#7045c99413b3eb94888d83ab46d0b404cc7bdde4" + integrity sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ= + dependencies: + extend "^3.0.0" + glob "^7.1.1" + glob-parent "^3.1.0" + is-negated-glob "^1.0.0" + ordered-read-streams "^1.0.0" + pumpify "^1.3.5" + readable-stream "^2.1.5" + remove-trailing-separator "^1.0.1" + to-absolute-glob "^2.0.0" + unique-stream "^2.0.2" + +glob-watcher@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-5.0.3.tgz#88a8abf1c4d131eb93928994bc4a593c2e5dd626" + integrity sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg== + dependencies: + anymatch "^2.0.0" + async-done "^1.2.0" + chokidar "^2.0.0" + is-negated-glob "^1.0.0" + just-debounce "^1.0.0" + object.defaults "^1.1.0" + +glob@3.2.8: + version "3.2.8" + resolved "https://registry.yarnpkg.com/glob/-/glob-3.2.8.tgz#5506f4311721bcc618c7d8dba144188750307073" + integrity sha1-VQb0MRchvMYYx9jboUQYh1AwcHM= + dependencies: + inherits "2" + minimatch "~0.2.11" + +glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@~7.1.1: + version "7.1.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" + integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-modules@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" + integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== + dependencies: + global-prefix "^1.0.1" + is-windows "^1.0.1" + resolve-dir "^1.0.0" + +global-prefix@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" + integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4= + dependencies: + expand-tilde "^2.0.2" + homedir-polyfill "^1.0.1" + ini "^1.3.4" + is-windows "^1.0.1" + which "^1.2.14" + +globule@^1.0.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.1.tgz#5dffb1b191f22d20797a9369b49eab4e9839696d" + integrity sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ== + dependencies: + glob "~7.1.1" + lodash "~4.17.10" + minimatch "~3.0.2" + +glogg@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.2.tgz#2d7dd702beda22eb3bffadf880696da6d846313f" + integrity sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA== + dependencies: + sparkles "^1.0.0" + +gonzales-pe@3.0.0-28: + version "3.0.0-28" + resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-3.0.0-28.tgz#dd50b41dd15b682a28c40e5f0ff2007901ac62bd" + integrity sha1-3VC0HdFbaCooxA5fD/IAeQGsYr0= + +graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: + version "4.2.1" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.1.tgz#1c1f0c364882c868f5bff6512146328336a11b1d" + integrity sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw== + +gulp-autoprefixer@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/gulp-autoprefixer/-/gulp-autoprefixer-6.1.0.tgz#5f7f78468fe99a589ce353fa5891b7bee16b8f1e" + integrity sha512-Ti/BUFe+ekhbDJfspZIMiOsOvw51KhI9EncsDfK7NaxjqRm+v4xS9v99kPxEoiDavpWqQWvG8Y6xT1mMlB3aXA== + dependencies: + autoprefixer "^9.5.1" + fancy-log "^1.3.2" + plugin-error "^1.0.1" + postcss "^7.0.2" + through2 "^3.0.1" + vinyl-sourcemaps-apply "^0.2.1" + +gulp-clean-css@^3.9.4: + version "3.10.0" + resolved "https://registry.yarnpkg.com/gulp-clean-css/-/gulp-clean-css-3.10.0.tgz#bccd4605eff104bfa4980014cc4b3c24c571736d" + integrity sha512-7Isf9Y690o/Q5MVjEylH1H7L8WeZ89woW7DnhD5unTintOdZb67KdOayRgp9trUFo+f9UyJtuatV42e/+kghPg== + dependencies: + clean-css "4.2.1" + plugin-error "1.0.1" + through2 "2.0.3" + vinyl-sourcemaps-apply "0.2.1" + +gulp-cli@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/gulp-cli/-/gulp-cli-2.2.0.tgz#5533126eeb7fe415a7e3e84a297d334d5cf70ebc" + integrity sha512-rGs3bVYHdyJpLqR0TUBnlcZ1O5O++Zs4bA0ajm+zr3WFCfiSLjGwoCBqFs18wzN+ZxahT9DkOK5nDf26iDsWjA== + dependencies: + ansi-colors "^1.0.1" + archy "^1.0.0" + array-sort "^1.0.0" + color-support "^1.1.3" + concat-stream "^1.6.0" + copy-props "^2.0.1" + fancy-log "^1.3.2" + gulplog "^1.0.0" + interpret "^1.1.0" + isobject "^3.0.1" + liftoff "^3.1.0" + matchdep "^2.0.0" + mute-stdout "^1.0.0" + pretty-hrtime "^1.0.0" + replace-homedir "^1.0.0" + semver-greatest-satisfied-range "^1.1.0" + v8flags "^3.0.1" + yargs "^7.1.0" + +gulp-csscomb@^3.0.8: + version "3.0.8" + resolved "https://registry.yarnpkg.com/gulp-csscomb/-/gulp-csscomb-3.0.8.tgz#df34824a580a4c7d3351c1e8ebb6ad7a1d5a89b7" + integrity sha1-3zSCSlgKTH0zUcHo67ateh1aibc= + dependencies: + csscomb "^3.1.7" + gulp-util "^3.0.7" + through2 "^2.0.1" + +gulp-rename@^1.2.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/gulp-rename/-/gulp-rename-1.4.0.tgz#de1c718e7c4095ae861f7296ef4f3248648240bd" + integrity sha512-swzbIGb/arEoFK89tPY58vg3Ok1bw+d35PfUNwWqdo7KM4jkmuGA78JiDNqR+JeZFaeeHnRg9N7aihX3YPmsyg== + +gulp-sass@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/gulp-sass/-/gulp-sass-4.0.2.tgz#cfb1e3eff2bd9852431c7ce87f43880807d8d505" + integrity sha512-q8psj4+aDrblJMMtRxihNBdovfzGrXJp1l4JU0Sz4b/Mhsi2DPrKFYCGDwjIWRENs04ELVHxdOJQ7Vs98OFohg== + dependencies: + chalk "^2.3.0" + lodash.clonedeep "^4.3.2" + node-sass "^4.8.3" + plugin-error "^1.0.1" + replace-ext "^1.0.0" + strip-ansi "^4.0.0" + through2 "^2.0.0" + vinyl-sourcemaps-apply "^0.2.0" + +gulp-sourcemaps@^2.6.4: + version "2.6.5" + resolved "https://registry.yarnpkg.com/gulp-sourcemaps/-/gulp-sourcemaps-2.6.5.tgz#a3f002d87346d2c0f3aec36af7eb873f23de8ae6" + integrity sha512-SYLBRzPTew8T5Suh2U8jCSDKY+4NARua4aqjj8HOysBh2tSgT9u4jc1FYirAdPx1akUxxDeK++fqw6Jg0LkQRg== + dependencies: + "@gulp-sourcemaps/identity-map" "1.X" + "@gulp-sourcemaps/map-sources" "1.X" + acorn "5.X" + convert-source-map "1.X" + css "2.X" + debug-fabulous "1.X" + detect-newline "2.X" + graceful-fs "4.X" + source-map "~0.6.0" + strip-bom-string "1.X" + through2 "2.X" + +gulp-util@^3.0.7: + version "3.0.8" + resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" + integrity sha1-AFTh50RQLifATBh8PsxQXdVLu08= + dependencies: + array-differ "^1.0.0" + array-uniq "^1.0.2" + beeper "^1.0.0" + chalk "^1.0.0" + dateformat "^2.0.0" + fancy-log "^1.1.0" + gulplog "^1.0.0" + has-gulplog "^0.1.0" + lodash._reescape "^3.0.0" + lodash._reevaluate "^3.0.0" + lodash._reinterpolate "^3.0.0" + lodash.template "^3.0.0" + minimist "^1.1.0" + multipipe "^0.1.2" + object-assign "^3.0.0" + replace-ext "0.0.1" + through2 "^2.0.0" + vinyl "^0.5.0" + +gulp@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/gulp/-/gulp-4.0.2.tgz#543651070fd0f6ab0a0650c6a3e6ff5a7cb09caa" + integrity sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA== + dependencies: + glob-watcher "^5.0.3" + gulp-cli "^2.2.0" + undertaker "^1.2.1" + vinyl-fs "^3.0.0" + +gulplog@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gulplog/-/gulplog-1.0.0.tgz#e28c4d45d05ecbbed818363ce8f9c5926229ffe5" + integrity sha1-4oxNRdBey77YGDY86PnFkmIp/+U= + dependencies: + glogg "^1.0.0" + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.0: + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" + integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + dependencies: + ajv "^6.5.5" + har-schema "^2.0.0" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= + dependencies: + ansi-regex "^2.0.0" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-gulplog@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/has-gulplog/-/has-gulplog-0.1.0.tgz#6414c82913697da51590397dafb12f22967811ce" + integrity sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4= + dependencies: + sparkles "^1.0.0" + +has-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" + integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +homedir-polyfill@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" + integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== + dependencies: + parse-passwd "^1.0.0" + +hosted-git-info@^2.1.4: + version "2.8.2" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.2.tgz#a35c3f355ac1249f1093c0c2a542ace8818c171a" + integrity sha512-CyjlXII6LMsPMyUzxpTt8fzh5QwzGqPmQXgY/Jyf4Zfp27t/FvfhwoE/8laaMUcMy816CkWF20I7NeQhwwY88w== + dependencies: + lru-cache "^5.1.1" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +iconv-lite@^0.4.4: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore-walk@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" + integrity sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ== + dependencies: + minimatch "^3.0.4" + +in-publish@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.0.tgz#e20ff5e3a2afc2690320b6dc552682a9c7fadf51" + integrity sha1-4g/146KvwmkDILbcVSaCqcf631E= + +indent-string@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" + integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= + dependencies: + repeating "^2.0.0" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@^1.3.4, ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + +interpret@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" + integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== + +invert-kv@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= + +is-absolute@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" + integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== + dependencies: + is-relative "^1.0.0" + is-windows "^1.0.1" + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= + dependencies: + binary-extensions "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + +is-extglob@^2.1.0, is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-finite@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" + integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko= + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= + dependencies: + is-extglob "^2.1.0" + +is-glob@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-negated-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" + integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI= + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= + dependencies: + kind-of "^3.0.2" + +is-number@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" + integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ== + +is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-promise@^2.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= + +is-relative@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" + integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== + dependencies: + is-unc-path "^1.0.0" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-unc-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" + integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== + dependencies: + unc-path-regex "^0.1.2" + +is-utf8@^0.2.0, is-utf8@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= + +is-valid-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa" + integrity sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao= + +is-windows@^1.0.1, is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + +isarray@1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +js-base64@^2.1.8: + version "2.5.1" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.1.tgz#1efa39ef2c5f7980bb1784ade4a8af2de3291121" + integrity sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw== + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +just-debounce@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/just-debounce/-/just-debounce-1.0.0.tgz#87fccfaeffc0b68cd19d55f6722943f929ea35ea" + integrity sha1-h/zPrv/AtozRnVX2cilD+SnqNeo= + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0, kind-of@^5.0.2: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" + integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== + +last-run@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/last-run/-/last-run-1.1.1.tgz#45b96942c17b1c79c772198259ba943bebf8ca5b" + integrity sha1-RblpQsF7HHnHchmCWbqUO+v4yls= + dependencies: + default-resolution "^2.0.0" + es6-weak-map "^2.0.1" + +lazystream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" + integrity sha1-9plf4PggOS9hOWvolGJAe7dxaOQ= + dependencies: + readable-stream "^2.0.5" + +lcid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= + dependencies: + invert-kv "^1.0.0" + +lead@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lead/-/lead-1.0.0.tgz#6f14f99a37be3a9dd784f5495690e5903466ee42" + integrity sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI= + dependencies: + flush-write-stream "^1.0.2" + +liftoff@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-3.1.0.tgz#c9ba6081f908670607ee79062d700df062c52ed3" + integrity sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog== + dependencies: + extend "^3.0.0" + findup-sync "^3.0.0" + fined "^1.0.1" + flagged-respawn "^1.0.0" + is-plain-object "^2.0.4" + object.map "^1.0.0" + rechoir "^0.6.2" + resolve "^1.1.7" + +load-json-file@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" + integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA= + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + pinkie-promise "^2.0.0" + strip-bom "^2.0.0" + +lodash._basecopy@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" + integrity sha1-jaDmqHbPNEwK2KVIghEd08XHyjY= + +lodash._basetostring@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz#d1861d877f824a52f669832dcaf3ee15566a07d5" + integrity sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U= + +lodash._basevalues@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz#5b775762802bde3d3297503e26300820fdf661b7" + integrity sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc= + +lodash._getnative@^3.0.0: + version "3.9.1" + resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" + integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U= + +lodash._isiterateecall@^3.0.0: + version "3.0.9" + resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" + integrity sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw= + +lodash._reescape@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reescape/-/lodash._reescape-3.0.0.tgz#2b1d6f5dfe07c8a355753e5f27fac7f1cde1616a" + integrity sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo= + +lodash._reevaluate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz#58bc74c40664953ae0b124d806996daca431e2ed" + integrity sha1-WLx0xAZklTrgsSTYBpltrKQx4u0= + +lodash._reinterpolate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" + integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= + +lodash._root@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" + integrity sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI= + +lodash.clonedeep@^4.3.2: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= + +lodash.escape@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-3.2.0.tgz#995ee0dc18c1b48cc92effae71a10aab5b487698" + integrity sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg= + dependencies: + lodash._root "^3.0.0" + +lodash.isarguments@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" + integrity sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo= + +lodash.isarray@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" + integrity sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U= + +lodash.keys@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" + integrity sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo= + dependencies: + lodash._getnative "^3.0.0" + lodash.isarguments "^3.0.0" + lodash.isarray "^3.0.0" + +lodash.restparam@^3.0.0: + version "3.6.1" + resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" + integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU= + +lodash.template@^3.0.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-3.6.2.tgz#f8cdecc6169a255be9098ae8b0c53d378931d14f" + integrity sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8= + dependencies: + lodash._basecopy "^3.0.0" + lodash._basetostring "^3.0.0" + lodash._basevalues "^3.0.0" + lodash._isiterateecall "^3.0.0" + lodash._reinterpolate "^3.0.0" + lodash.escape "^3.0.0" + lodash.keys "^3.0.0" + lodash.restparam "^3.0.0" + lodash.templatesettings "^3.0.0" + +lodash.templatesettings@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz#fb307844753b66b9f1afa54e262c745307dba8e5" + integrity sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU= + dependencies: + lodash._reinterpolate "^3.0.0" + lodash.escape "^3.0.0" + +lodash@^4.0.0, lodash@^4.17.11, lodash@~4.17.10: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + +loud-rejection@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" + integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= + dependencies: + currently-unhandled "^0.4.1" + signal-exit "^3.0.0" + +lru-cache@2: + version "2.7.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" + integrity sha1-bUUk6LlV+V1PW1iFHOId1y+06VI= + +lru-cache@^4.0.1: + version "4.1.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" + integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-queue@0.1: + version "0.1.0" + resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" + integrity sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM= + dependencies: + es5-ext "~0.10.2" + +make-iterator@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" + integrity sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw== + dependencies: + kind-of "^6.0.2" + +map-cache@^0.2.0, map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + +map-obj@^1.0.0, map-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + dependencies: + object-visit "^1.0.0" + +matchdep@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/matchdep/-/matchdep-2.0.0.tgz#c6f34834a0d8dbc3b37c27ee8bbcb27c7775582e" + integrity sha1-xvNINKDY28OzfCfui7yyfHd1WC4= + dependencies: + findup-sync "^2.0.0" + micromatch "^3.0.4" + resolve "^1.4.0" + stack-trace "0.0.10" + +memoizee@0.4.X: + version "0.4.14" + resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.14.tgz#07a00f204699f9a95c2d9e77218271c7cd610d57" + integrity sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg== + dependencies: + d "1" + es5-ext "^0.10.45" + es6-weak-map "^2.0.2" + event-emitter "^0.3.5" + is-promise "^2.1" + lru-queue "0.1" + next-tick "1" + timers-ext "^0.1.5" + +meow@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" + integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= + dependencies: + camelcase-keys "^2.0.0" + decamelize "^1.1.2" + loud-rejection "^1.0.0" + map-obj "^1.0.1" + minimist "^1.1.3" + normalize-package-data "^2.3.4" + object-assign "^4.0.1" + read-pkg-up "^1.0.1" + redent "^1.0.0" + trim-newlines "^1.0.0" + +micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +mime-db@1.40.0: + version "1.40.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" + integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.24" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" + integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ== + dependencies: + mime-db "1.40.0" + +minimatch@0.2.12: + version "0.2.12" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.2.12.tgz#ea82a012ac662c7ddfaa144f1c147e6946f5dafb" + integrity sha1-6oKgEqxmLH3fqhRPHBR+aUb12vs= + dependencies: + lru-cache "2" + sigmund "~1.0.0" + +minimatch@^3.0.4, minimatch@~3.0.2: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimatch@~0.2.11: + version "0.2.14" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.2.14.tgz#c74e780574f63c6f9a090e90efbe6ef53a6a756a" + integrity sha1-x054BXT2PG+aCQ6Q775u9TpqdWo= + dependencies: + lru-cache "2" + sigmund "~1.0.0" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= + +minipass@^2.2.1, minipass@^2.3.5: + version "2.3.5" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" + integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA== + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + +minizlib@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" + integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA== + dependencies: + minipass "^2.2.1" + +mixin-deep@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +multipipe@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-0.1.2.tgz#2a8f2ddf70eed564dff2d57f1e1a137d9f05078b" + integrity sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s= + dependencies: + duplexer2 "0.0.2" + +mute-stdout@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mute-stdout/-/mute-stdout-1.0.1.tgz#acb0300eb4de23a7ddeec014e3e96044b3472331" + integrity sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg== + +nan@^2.12.1, nan@^2.13.2: + version "2.14.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" + integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +needle@^2.2.1: + version "2.4.0" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c" + integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg== + dependencies: + debug "^3.2.6" + iconv-lite "^0.4.4" + sax "^1.2.4" + +next-tick@1, next-tick@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" + integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= + +node-gyp@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c" + integrity sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA== + dependencies: + fstream "^1.0.0" + glob "^7.0.3" + graceful-fs "^4.1.2" + mkdirp "^0.5.0" + nopt "2 || 3" + npmlog "0 || 1 || 2 || 3 || 4" + osenv "0" + request "^2.87.0" + rimraf "2" + semver "~5.3.0" + tar "^2.0.0" + which "1" + +node-pre-gyp@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149" + integrity sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A== + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.1" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4" + +node-releases@^1.1.25: + version "1.1.26" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.26.tgz#f30563edc5c7dc20cf524cc8652ffa7be0762937" + integrity sha512-fZPsuhhUHMTlfkhDLGtfY80DSJTjOcx+qD1j5pqPkuhUHVS7xHZIg9EE4DHK8O3f0zTxXHX5VIkDG8pu98/wfQ== + dependencies: + semver "^5.3.0" + +node-sass@^4.8.3: + version "4.12.0" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.12.0.tgz#0914f531932380114a30cc5fa4fa63233a25f017" + integrity sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ== + dependencies: + async-foreach "^0.1.3" + chalk "^1.1.1" + cross-spawn "^3.0.0" + gaze "^1.0.0" + get-stdin "^4.0.1" + glob "^7.0.3" + in-publish "^2.0.0" + lodash "^4.17.11" + meow "^3.7.0" + mkdirp "^0.5.1" + nan "^2.13.2" + node-gyp "^3.8.0" + npmlog "^4.0.0" + request "^2.88.0" + sass-graph "^2.2.4" + stdout-stream "^1.4.0" + "true-case-path" "^1.0.2" + +node-uuid@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.0.tgz#07f9b2337572ff6275c775e1d48513f3a45d7a65" + integrity sha1-B/myM3Vy/2J1x3Xh1IUT86RdemU= + +"nopt@2 || 3": + version "3.0.6" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" + integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k= + dependencies: + abbrev "1" + +nopt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= + dependencies: + abbrev "1" + osenv "^0.1.4" + +normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^2.0.1, normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + dependencies: + remove-trailing-separator "^1.0.1" + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= + +now-and-later@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.1.tgz#8e579c8685764a7cc02cb680380e94f43ccb1f7c" + integrity sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ== + dependencies: + once "^1.3.2" + +npm-bundled@^1.0.1: + version "1.0.6" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" + integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g== + +npm-packlist@^1.1.6: + version "1.4.4" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.4.tgz#866224233850ac534b63d1a6e76050092b5d2f44" + integrity sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw== + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + +"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +object-assign@4.X, object-assign@^4.0.1, object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-assign@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" + integrity sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I= + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-keys@^1.0.11, object-keys@^1.0.12: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + dependencies: + isobject "^3.0.0" + +object.assign@^4.0.4: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +object.defaults@^1.0.0, object.defaults@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" + integrity sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8= + dependencies: + array-each "^1.0.1" + array-slice "^1.0.0" + for-own "^1.0.0" + isobject "^3.0.0" + +object.map@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object.map/-/object.map-1.0.1.tgz#cf83e59dc8fcc0ad5f4250e1f78b3b81bd801d37" + integrity sha1-z4Plncj8wK1fQlDh94s7gb2AHTc= + dependencies: + for-own "^1.0.0" + make-iterator "^1.0.0" + +object.pick@^1.2.0, object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + dependencies: + isobject "^3.0.1" + +object.reduce@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object.reduce/-/object.reduce-1.0.1.tgz#6fe348f2ac7fa0f95ca621226599096825bb03ad" + integrity sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60= + dependencies: + for-own "^1.0.0" + make-iterator "^1.0.0" + +once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +ordered-read-streams@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e" + integrity sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4= + dependencies: + readable-stream "^2.0.1" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= + +os-locale@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= + dependencies: + lcid "^1.0.0" + +os-tmpdir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +osenv@0, osenv@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +parse-filepath@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" + integrity sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE= + dependencies: + is-absolute "^1.0.0" + map-cache "^0.2.0" + path-root "^0.1.1" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= + dependencies: + error-ex "^1.2.0" + +parse-node-version@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b" + integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA== + +parse-passwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" + integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= + +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s= + dependencies: + pinkie-promise "^2.0.0" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +path-root-regex@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d" + integrity sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0= + +path-root@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7" + integrity sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc= + dependencies: + path-root-regex "^0.1.0" + +path-type@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE= + dependencies: + graceful-fs "^4.1.2" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +pify@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= + +plugin-error@1.0.1, plugin-error@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-1.0.1.tgz#77016bd8919d0ac377fdcdd0322328953ca5781c" + integrity sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA== + dependencies: + ansi-colors "^1.0.1" + arr-diff "^4.0.0" + arr-union "^3.1.0" + extend-shallow "^3.0.2" + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + +postcss-value-parser@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz#482282c09a42706d1fc9a069b73f44ec08391dc9" + integrity sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ== + +postcss@^7.0.17, postcss@^7.0.2: + version "7.0.17" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.17.tgz#4da1bdff5322d4a0acaab4d87f3e782436bad31f" + integrity sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + +pretty-hrtime@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" + integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= + +process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= + +psl@^1.1.24: + version "1.3.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.3.0.tgz#e1ebf6a3b5564fa8376f3da2275da76d875ca1bd" + integrity sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag== + +pump@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" + integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pumpify@^1.3.5: + version "1.5.1" + resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" + integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== + dependencies: + duplexify "^3.6.0" + inherits "^2.0.3" + pump "^2.0.0" + +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +read-pkg-up@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI= + dependencies: + find-up "^1.0.0" + read-pkg "^1.0.0" + +read-pkg@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" + integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg= + dependencies: + load-json-file "^1.0.0" + normalize-package-data "^2.3.2" + path-type "^1.0.0" + +"readable-stream@2 || 3": + version "3.4.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc" + integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@~1.1.9: + version "1.1.14" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readdirp@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" + integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== + dependencies: + graceful-fs "^4.1.11" + micromatch "^3.1.10" + readable-stream "^2.0.2" + +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + integrity sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q= + dependencies: + resolve "^1.1.6" + +redent@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" + integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94= + dependencies: + indent-string "^2.1.0" + strip-indent "^1.0.1" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +remove-bom-buffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz#c2bf1e377520d324f623892e33c10cac2c252b53" + integrity sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ== + dependencies: + is-buffer "^1.1.5" + is-utf8 "^0.2.1" + +remove-bom-stream@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz#05f1a593f16e42e1fb90ebf59de8e569525f9523" + integrity sha1-BfGlk/FuQuH7kOv1nejlaVJflSM= + dependencies: + remove-bom-buffer "^3.0.0" + safe-buffer "^5.1.0" + through2 "^2.0.3" + +remove-trailing-separator@^1.0.1, remove-trailing-separator@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +repeat-element@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= + dependencies: + is-finite "^1.0.0" + +replace-ext@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" + integrity sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ= + +replace-ext@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" + integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= + +replace-homedir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/replace-homedir/-/replace-homedir-1.0.0.tgz#e87f6d513b928dde808260c12be7fec6ff6e798c" + integrity sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw= + dependencies: + homedir-polyfill "^1.0.1" + is-absolute "^1.0.0" + remove-trailing-separator "^1.1.0" + +request@^2.87.0, request@^2.88.0: + version "2.88.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" + integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.0" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.4.3" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-main-filename@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" + integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= + +resolve-dir@^1.0.0, resolve-dir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" + integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M= + dependencies: + expand-tilde "^2.0.0" + global-modules "^1.0.0" + +resolve-options@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/resolve-options/-/resolve-options-1.1.0.tgz#32bb9e39c06d67338dc9378c0d6d6074566ad131" + integrity sha1-MrueOcBtZzONyTeMDW1gdFZq0TE= + dependencies: + value-or-function "^3.0.0" + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + +resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.4.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6" + integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w== + dependencies: + path-parse "^1.0.6" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + +rimraf@2, rimraf@^2.6.1: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sass-graph@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49" + integrity sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k= + dependencies: + glob "^7.0.0" + lodash "^4.0.0" + scss-tokenizer "^0.2.3" + yargs "^7.0.0" + +sax@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +scss-tokenizer@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" + integrity sha1-jrBtualyMzOCTT9VMGQRSYR85dE= + dependencies: + js-base64 "^2.1.8" + source-map "^0.4.2" + +semver-greatest-satisfied-range@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b" + integrity sha1-E+jCZYq5aRywzXEJMkAoDTb3els= + dependencies: + sver-compat "^1.5.0" + +"semver@2 || 3 || 4 || 5", semver@^5.3.0: + version "5.7.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" + integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== + +semver@~5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" + integrity sha1-myzl094C0XxgEq0yaqa00M9U+U8= + +set-blocking@^2.0.0, set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +set-value@^2.0.0, set-value@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" + integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +sigmund@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" + integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA= + +signal-exit@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" + integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== + dependencies: + atob "^2.1.1" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + +source-map@^0.4.2: + version "0.4.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" + integrity sha1-66T12pwNyZneaAMti092FzZSA2s= + dependencies: + amdefine ">=0.0.4" + +source-map@^0.5.1, source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sparkles@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.1.tgz#008db65edce6c50eec0c5e228e1945061dd0437c" + integrity sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw== + +spdx-correct@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" + integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" + integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== + +spdx-expression-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" + integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.5" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" + integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +stack-trace@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +stdout-stream@^1.4.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.1.tgz#5ac174cdd5cd726104aa0c0b2bd83815d8d535de" + integrity sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA== + dependencies: + readable-stream "^2.0.1" + +stream-exhaust@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/stream-exhaust/-/stream-exhaust-1.0.2.tgz#acdac8da59ef2bc1e17a2c0ccf6c320d120e555d" + integrity sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw== + +stream-shift@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" + integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI= + +string-width@^1.0.1, string-width@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +"string-width@^1.0.2 || 2": + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-bom-string@1.X: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" + integrity sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI= + +strip-bom@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4= + dependencies: + is-utf8 "^0.2.0" + +strip-indent@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" + integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI= + dependencies: + get-stdin "^4.0.1" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + +sver-compat@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/sver-compat/-/sver-compat-1.5.0.tgz#3cf87dfeb4d07b4a3f14827bc186b3fd0c645cd8" + integrity sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg= + dependencies: + es6-iterator "^2.0.1" + es6-symbol "^3.1.1" + +tar@^2.0.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40" + integrity sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA== + dependencies: + block-stream "*" + fstream "^1.0.12" + inherits "2" + +tar@^4: + version "4.4.10" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.10.tgz#946b2810b9a5e0b26140cf78bea6b0b0d689eba1" + integrity sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA== + dependencies: + chownr "^1.1.1" + fs-minipass "^1.2.5" + minipass "^2.3.5" + minizlib "^1.2.1" + mkdirp "^0.5.0" + safe-buffer "^5.1.2" + yallist "^3.0.3" + +through2-filter@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-3.0.0.tgz#700e786df2367c2c88cd8aa5be4cf9c1e7831254" + integrity sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA== + dependencies: + through2 "~2.0.0" + xtend "~4.0.0" + +through2@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" + integrity sha1-AARWmzfHx0ujnEPzzteNGtlBQL4= + dependencies: + readable-stream "^2.1.5" + xtend "~4.0.1" + +through2@2.X, through2@^2.0.0, through2@^2.0.1, through2@^2.0.3, through2@~2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +through2@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.1.tgz#39276e713c3302edf9e388dd9c812dd3b825bd5a" + integrity sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww== + dependencies: + readable-stream "2 || 3" + +time-stamp@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" + integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM= + +timers-ext@^0.1.5: + version "0.1.7" + resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6" + integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ== + dependencies: + es5-ext "~0.10.46" + next-tick "1" + +to-absolute-glob@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1865f43d9e74b0822db9f145b78cff7d0f7c849b" + integrity sha1-GGX0PZ50sIItufFFt4z/fQ98hJs= + dependencies: + is-absolute "^1.0.0" + is-negated-glob "^1.0.0" + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +to-through@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-through/-/to-through-2.0.0.tgz#fc92adaba072647bc0b67d6b03664aa195093af6" + integrity sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY= + dependencies: + through2 "^2.0.3" + +tough-cookie@~2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" + integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== + dependencies: + psl "^1.1.24" + punycode "^1.4.1" + +trim-newlines@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" + integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= + +"true-case-path@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.3.tgz#f813b5a8c86b40da59606722b144e3225799f47d" + integrity sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew== + dependencies: + glob "^7.1.2" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +type@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/type/-/type-1.0.3.tgz#16f5d39f27a2d28d86e48f8981859e9d3296c179" + integrity sha512-51IMtNfVcee8+9GJvj0spSuFcZHe9vSib6Xtgsny1Km9ugyz2mbS08I3rsUIRYgJohFRFU1160sgRodYz378Hg== + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= + +unc-path-regex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" + integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= + +undertaker-registry@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/undertaker-registry/-/undertaker-registry-1.0.1.tgz#5e4bda308e4a8a2ae584f9b9a4359a499825cc50" + integrity sha1-XkvaMI5KiirlhPm5pDWaSZglzFA= + +undertaker@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/undertaker/-/undertaker-1.2.1.tgz#701662ff8ce358715324dfd492a4f036055dfe4b" + integrity sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA== + dependencies: + arr-flatten "^1.0.1" + arr-map "^2.0.0" + bach "^1.0.0" + collection-map "^1.0.0" + es6-weak-map "^2.0.1" + last-run "^1.1.0" + object.defaults "^1.0.0" + object.reduce "^1.0.0" + undertaker-registry "^1.0.0" + +union-value@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^2.0.1" + +unique-stream@^2.0.2: + version "2.3.1" + resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.3.1.tgz#c65d110e9a4adf9a6c5948b28053d9a8d04cbeac" + integrity sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A== + dependencies: + json-stable-stringify-without-jsonify "^1.0.1" + through2-filter "^3.0.0" + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +upath@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068" + integrity sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q== + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +uuid@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== + +v8flags@^3.0.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.1.3.tgz#fc9dc23521ca20c5433f81cc4eb9b3033bb105d8" + integrity sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w== + dependencies: + homedir-polyfill "^1.0.1" + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +value-or-function@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813" + integrity sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM= + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +vinyl-fs@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.3.tgz#c85849405f67428feabbbd5c5dbdd64f47d31bc7" + integrity sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng== + dependencies: + fs-mkdirp-stream "^1.0.0" + glob-stream "^6.1.0" + graceful-fs "^4.0.0" + is-valid-glob "^1.0.0" + lazystream "^1.0.0" + lead "^1.0.0" + object.assign "^4.0.4" + pumpify "^1.3.5" + readable-stream "^2.3.3" + remove-bom-buffer "^3.0.0" + remove-bom-stream "^1.2.0" + resolve-options "^1.1.0" + through2 "^2.0.0" + to-through "^2.0.0" + value-or-function "^3.0.0" + vinyl "^2.0.0" + vinyl-sourcemap "^1.1.0" + +vinyl-sourcemap@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz#92a800593a38703a8cdb11d8b300ad4be63b3e16" + integrity sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY= + dependencies: + append-buffer "^1.0.2" + convert-source-map "^1.5.0" + graceful-fs "^4.1.6" + normalize-path "^2.1.1" + now-and-later "^2.0.0" + remove-bom-buffer "^3.0.0" + vinyl "^2.0.0" + +vinyl-sourcemaps-apply@0.2.1, vinyl-sourcemaps-apply@^0.2.0, vinyl-sourcemaps-apply@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz#ab6549d61d172c2b1b87be5c508d239c8ef87705" + integrity sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU= + dependencies: + source-map "^0.5.1" + +vinyl@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.5.3.tgz#b0455b38fc5e0cf30d4325132e461970c2091cde" + integrity sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4= + dependencies: + clone "^1.0.0" + clone-stats "^0.0.1" + replace-ext "0.0.1" + +vinyl@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.0.tgz#d85b07da96e458d25b2ffe19fece9f2caa13ed86" + integrity sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg== + dependencies: + clone "^2.1.1" + clone-buffer "^1.0.0" + clone-stats "^1.0.0" + cloneable-readable "^1.0.0" + remove-trailing-separator "^1.0.1" + replace-ext "^1.0.0" + +vow-fs@0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/vow-fs/-/vow-fs-0.3.2.tgz#ea2b034d85e1db8c277eb2e9a86d1c15f5d38e7a" + integrity sha1-6isDTYXh24wnfrLpqG0cFfXTjno= + dependencies: + glob "3.2.8" + node-uuid "1.4.0" + vow "0.4.4" + vow-queue "0.3.1" + +vow-queue@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/vow-queue/-/vow-queue-0.3.1.tgz#598c51a15b0a81a6d5fc05f4761ceb462de1e868" + integrity sha1-WYxRoVsKgabV/AX0dhzrRi3h6Gg= + dependencies: + vow "~0.4.0" + +vow@0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/vow/-/vow-0.4.4.tgz#c9fe4609129d7f5aa621508ebe64b51c95bc7b98" + integrity sha1-yf5GCRKdf1qmIVCOvmS1HJW8e5g= + +vow@~0.4.0: + version "0.4.20" + resolved "https://registry.yarnpkg.com/vow/-/vow-0.4.20.tgz#77ca6ef0828e0043a93e55dc37030226519ce711" + integrity sha512-YYoSYXUYABqY08D/WrjcWJxJSErcILRRTQpcPyUc0SFfgIPKSUFzVt7u1HC3TXGJZM/qhsSjCLNQstxqf7asgQ== + +which-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" + integrity sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8= + +which@1, which@^1.2.14, which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +xtend@~4.0.0, xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y18n@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" + integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= + +yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" + integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== + +yargs-parser@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a" + integrity sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo= + dependencies: + camelcase "^3.0.0" + +yargs@^7.0.0, yargs@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" + integrity sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg= + dependencies: + camelcase "^3.0.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^1.4.0" + read-pkg-up "^1.0.1" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^1.0.2" + which-module "^1.0.0" + y18n "^3.2.1" + yargs-parser "^5.0.0" diff --git a/webserver-configs/Caddyfile b/webserver-configs/Caddyfile new file mode 100644 index 0000000..cfceced --- /dev/null +++ b/webserver-configs/Caddyfile @@ -0,0 +1,31 @@ +# To use this file simply install caddy and run the command below from the root of your Grav site +# Once running it will redirect http://localhost to https://localhost (new default for Caddy2) +# More infromation here: https://caddyserver.com/docs/ +# +# $ caddy run --config webserver-configs/Caddyfile + +localhost +encode gzip +root * . +file_server + +php_fastcgi 127.0.0.1:9000 + +# Begin - Security +# deny all direct access for these folders +rewrite /(\.git|cache|bin|logs|backups|tests)/.* /403 + +# deny running scripts inside core system folders +rewrite /(system|vendor)/.*\.(txt|xml|md|html|htm|shtml|shtm|yaml|yml|php|php2|php3|php4|php5|phar|phtml|pl|py|cgi|twig|sh|bat)$ /403 + +# deny running scripts inside user folder +rewrite /user/.*\.(txt|md|yaml|yml|php|php2|php3|php4|php5|phar|phtml|pl|py|cgi|twig|sh|bat)$ /403 + +# deny access to specific files in the root folder +rewrite /(LICENSE\.txt|composer\.lock|composer\.json|nginx\.conf|web\.config|htaccess\.txt|\.htaccess) /403 + +respond /403 403 +## End - Security + +# global rewrite should come last. +try_files {path} {path}/ /index.php?_url={uri}&{query} diff --git a/webserver-configs/Caddyfile-0.8.x b/webserver-configs/Caddyfile-0.8.x new file mode 100644 index 0000000..9e977a9 --- /dev/null +++ b/webserver-configs/Caddyfile-0.8.x @@ -0,0 +1,33 @@ +# Caddyfile for Caddy 0.8.x and below + +:8080 +gzip +fastcgi / 127.0.0.1:9000 php + +# Begin - Security +# deny all direct access for these folders +rewrite { + r /(\.git|cache|bin|logs|backups|tests)/.*$ + status 403 +} +# deny running scripts inside core system folders +rewrite { + r /(system|vendor)/.*\.(txt|xml|md|html|htm|shtml|shtm|yaml|yml|php|php2|php3|php4|php5|phar|phtml|pl|py|cgi|twig|sh|bat)$ + status 403 +} +# deny running scripts inside user folder +rewrite { + r /user/.*\.(txt|md|yaml|yml|php|php2|php3|php4|php5|phar|phtml|pl|py|cgi|twig|sh|bat)$ + status 403 +} +# deny access to specific files in the root folder +rewrite { + r /(LICENSE\.txt|composer\.lock|composer\.json|nginx\.conf|web\.config|htaccess\.txt|\.htaccess) + status 403 +} +## End - Security + +# global rewrite should come last. +rewrite { + to {path} {path}/ /index.php?_url={uri}&{query} +} diff --git a/webserver-configs/htaccess.txt b/webserver-configs/htaccess.txt new file mode 100644 index 0000000..15436a7 --- /dev/null +++ b/webserver-configs/htaccess.txt @@ -0,0 +1,78 @@ + + +RewriteEngine On + +## Begin RewriteBase +# If you are getting 500 or 404 errors on subpages, you may have to uncomment the RewriteBase entry +# You should change the '/' to your appropriate subfolder. For example if you have +# your Grav install at the root of your site '/' should work, else it might be something +# along the lines of: RewriteBase / +## + +# RewriteBase / + +## End - RewriteBase + +## Begin - X-Forwarded-Proto +# In some hosted or load balanced environments, SSL negotiation happens upstream. +# In order for Grav to recognize the connection as secure, you need to uncomment +# the following lines. +# +# RewriteCond %{HTTP:X-Forwarded-Proto} https +# RewriteRule .* - [E=HTTPS:on] +# +## End - X-Forwarded-Proto + +## Begin - Exploits +# If you experience problems on your site block out the operations listed below +# This attempts to block the most common type of exploit `attempts` to Grav +# +# Block out any script trying to use twig tags in URL. +RewriteCond %{REQUEST_URI} ({{|}}|{%|%}) [OR] +RewriteCond %{QUERY_STRING} ({{|}}|{%25|%25}) [OR] +# Block out any script trying to base64_encode data within the URL. +RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR] +# Block out any script that includes a

    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 literal 0 HcmV?d00001 diff --git a/user/themes/test/fonts/line-awesome.woff2 b/user/themes/test/fonts/line-awesome.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..f825cfbef34b78e04206ac7d28551ad6fe3cfe8d GIT binary patch literal 76372 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

    6vs!JD|A3prFY#&hj8}}^K_83jompiH7 zwzY)-9aes{`>nY+Gb|2M##1ubgMwTOnx2w@&H#z*I6Lan@jGwJD<#Lr--DGVQSMb} zvd627{V}e}Z>m^j@5?JvBE5^PNJoKzP~1DuGAG(;n(-Lv2}92~+;%ZDt=AP|kkpuh z;neVKVnKv(G4M^cNrY1CFQuvJ!s%pJQoeXU==mf|x8I z+(!4&`9Mf;0GaC&H{8d!b=m4qpM5U2$!%yODX@6=<}cJ+Y6ry{sRrBYb0-?H@K2-P z9xMJrk)+h%rq~g~dp9$b3!ch4{VHuxbHIb@3B9_47iV-|R>rCy_Kj2eh%38E)*~)A zh|foujgeM*ZFe8#qT;13>h+OJZ?e|x8Grehi=qW2@_1K6 zNvQ_vV^Mb_F$OkB7caiwr8~ zq5-z-7p(*;Pk72d3hVxI3VdC=_gX`PQ>5QM5s3`_0a_F(du8)&jV=7}Qv_`iV4ZDL zmzL2(Xzd3}V<|4G4+yOug8b9u12|1)Z=z1(^`+Q-6am{K?|`9dskh_NaIccvk&SG1 zix{Z9Mvw^pbW+u>QY)75)8UYyHHB>S%t+Rhj=bguqRQiBbIyX#A3J1y9g_FlJxWs4 z@iwEnX*gZ=Elk&lC6r}W9n>;6$|Xea(CHN$-|pPGJzUE-V27+-?(vQB9oxvNCp+$Y z-#uc-6o%H+UrXCfI%*MmYrNKI_R_+=D*@#!C(zja-3zk<{=<>Mn(Og{9+tDr>rr9a z+M)@&16_>X6vwr|Ls^JPdvxjZKwUBEKb+?OSKpm9HtNX214F*>zlNpRdDCJO_Xqy~ D;HKE@ literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-msg.png b/system/images/media/thumb-msg.png new file mode 100644 index 0000000000000000000000000000000000000000..624ac17c9f5e9483d42622e251ff1229b2187656 GIT binary patch literal 3319 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-msi.png b/system/images/media/thumb-msi.png new file mode 100644 index 0000000000000000000000000000000000000000..96473f5e0dce769e972573f82147b20088b53f4b GIT binary patch literal 2594 zcmcH*2UAlCle-IDBvkM7P(nEyDxwrY$~-SXI0dB1Cn}tPf(UX5ND1XGC`Ks;6b11F zIcc5{;8X}Egen0Plp+uaNDaLP-g|i^ANT!-+nJr6-JRXpovnAzIN2*H>{O7Fl2US@ zkX@yuzG3`>a^G!9sxQ6Aw+JBsa0H~JD(@?zm!!A&Z^B*ePfC?{YRyVXeQR*$w7ac+ z4fyv6vJBxb`AFj=n$!W`>x3l|Nn9H|yMf~p$<0=H6~@Jw1j8jivp`A>IJJ)dmjewg zV=yW?$AQ*hyiJJuaG{YE>}j9)?I^Nkc8UeAKzIosiRHlyo45lDba@Bg3h&I@gNe|2%(rHt6ThHK>T_X;f$lfO;Gy``ehMI>w+iO@%T1) zT7*CE7jGc=z#@iVlG-t3LyVUVBcrQW_Y=e!Yzf3Y*q~oMbn64` z-vC{1fFfJruoh@|1-sr1jjdt53)ts5%##C(#5lDL&iEvL(kt$s$0Fau54*(${bIoi zcDfeys)H`nK`m2g`7pv0q9M)D*H!G@82aps__uaAu^mqS2v-V_)Gj#UJybt|-e`eb z-+>)7XoUcwb-+0M~*Q10OuHSn8yEEgyu0g=z4#l!X zbHAvA^Rk3WS^1k!_MQgiUDeHXFPu}yOt@xtMTR809?$Uosx%f)BU?i|xY`oi0KHyC|9pp8h{0jO&Q$uq+VBM#_y3GRJC%8%piqok{1;v z+3U}(qstPiPHjJWWHTm$I`a*U?ZDvhyAo%Teh>-V%_+&3cIXIW&Zz*ZvN2Ee7VjO% zDvmey_o7Xf9bJ06G3v$Axlg)FD-n+6Q3D@qcHX-nqa*85HeM%cbfLBq^#LNbFgCd| zAf3@as`{d&ar1V_S+MF7VRhTllPAuSZ!QRuflYF_h&X&2mMz|laq|Fp}y3Knl z3-U2NW~HprLSPG*(iK{^mwt@r6yh*7|C~QBA)C{-1+KFA`X14&q&gWJ=(E6K4L}4NGb_^bJ@*DskbiTrCS2P zw;t+{q5e2%OEjn7B=0_$X!2`ZT#hqi)2#f9MHB<}AgClwZYUb&VnJVvR;tAM0RQx& z`;!~$r8RcM{qzIt z={u?Bpq#FB4w0PxZPe|=XX}puzPh97(>!NAx;mMAyxOZ&P!!4^)up@oC6=Vgz!@Z8 zB33)}a%|nPzkuyQEHv%E8|9*+zw-r|`oO26HTujULQJoN8|gYVa#F`MZ7aJ#ss|98 zvbr-^hmwN_oo)Mpt6}b?1rUuWIngzr3bcT7>PY8bzTljx8hM-R5f_CUeHxP))(OnZ z^KPQ&kGBF>3C((GB~+;L&Z`)z+AtS?`j*B6%4?T>cFyP!wL`JT;O z)oh$7QQww$jp0`=^tuboO~@KyGPBCb-#>19O%BII{^n;c7wSZ zTO&fw%ds?ZAin8A!u|VHZHY`0Uz*e4n6-~?ad9R~uaAi2Y7kUt!#yK6>rQ=7E_KjY z+MRU2Rj5LiD@eX2`H|_YMK&oMG@Mbo(rILMGMD7aSb1>3taK9Kr>nHs#HbSl_^YGN zv3HWx{gYdkxLUitSmw1iN=>8wtVt_!P%5n4m$vQ|Ku=tl&)&>Erl^jGn)n^|IlgAw u!N@HBLO~$(eY@2t>oshJ2=X}zY9{Rzfk5j# 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 P{|z!Wu+T3#f8)u&1|*n; literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-odb.png b/system/images/media/thumb-odb.png new file mode 100644 index 0000000000000000000000000000000000000000..31fa431ba537d918aa68b083cbbb6f54d42b6b5b GIT binary patch literal 2912 zcmcJR`#Tek7soXuy7GxGQv0~1qIA;@84+@=NGO+bSGnh|_EDNo#Yl?WC-;%-FpFGf zGc#sp8*>>pw`~lYxy;x1`Th;x^E~G{=e(ZtocAy1dCp0&u`<~&b6iGDOl-fIsnG*5 zv0c#r`X2Eef^b8Nc7lYLfu(_%SXI*AO*hG%dROoR6GO3z-V?K8VgNH63tMCJa&$re z*a(|fOP%_J9_nLqrk7T?x3}N759Sc)S-tc!(!`cXWC=%$MBAG?+4gp9>p&M{Ua+y5 zf~794t*>otTEn}9n_I56I0S9l3f{$7Tv-urjB%DN%F!q~tA;X_-!~f2MA+C8F?sx{ zrB&NXOvGj|CYw|__>&3*kW&fJQ?vmVdk3!l~XOmJ7q1}9+sWByG9{`%&tM#BAyuCLuR zml|9$hWe8@7L4oz{~?iQ7a<+wN;2acZn$ZLncYiI!wo;F?yjdzmk%;B@gweaJy9)0 z(k%C7BLOox|E87HGqnI8WZ3<}bWbg$cGL3uM*sOk@~X$D_Y4oraA%hJAJIc}HgA-@ z{7>89Cp4vFVlJa+rl z(`?H8(%|gkC}%kc**7$|SV^AjWwN;f;r!}aQWu55WVepaw2sZhwGE;fbDz5?=!rSx z2&-Y3`5ia>8r2`uG7#TE_G{>!T^7(5mP$wyZR4{aI>;ph;~w=riRfjznAn~=Gb01r za2lH~2$SzRhF+32rR|+56=+C@f^QiDrKR%#`JEwq@Ojh_MT_8zg-1uCxTr2G>nB8< zi-Is9W#Os#^tNdtM{P?b0Bl%beV@CzKu*5YvYF4f=R1;laAz~h{Q^5`-%_zOqS8i z1>DvGLuwYX4eCy?ZMcL$G(=W==WPY99F%|ex#Qcx9#!aY+Vs@}gp1HO8d2KA91Ef~ zUFniU#B=}Nt56@Io{Op+rZywzai+;RdoL+{r%RKdLKWeZ>t*nb-XekHag)whcv)}M zwPjFYi_~j7wZ9-W>~~)yYW0fr05EmAJsp7@YK{faimkkNIeg~lQI%hTFXg~DG%l$B z1qp7zH|ZuWe9{I?A4%EiEFDOYVCV92ljlT!8C{SED;g2;M zMW`n!6ZBU4d|F(E&h zcoX#t=20AoN;?QX9ydRagS`(@Bp6QyC2}7e&C89fU1$S1(hz) zjHotKl*o5ZGs$@9dkEqirmf>^e$p;3-{$&B^7>th>D=!8UaSfntqO%zcp_g&7Z`lP z>l->*CoDT<-3QJYL_jtvKxhla0Z%)9k73^aW9+bs5ONHXv#XM2h)a`z{_-I)Z#@Z-3n>cS@|xRaxxHs(uf6eJat}{cqD9a zS;{()z3|I5AlsP55^F8!`?59;->cG+1An7eeW$3{;cB|1GEV%jK=Ftkl#6DaYSQ`q z5s^*Ex2D#p_8VY~o?CD;h@hfXxATdPtV^2Z2=DrUoT%Y! z(hN~6jdyf6QB}e6s8mXnxr2LR)Z~Gg6I3Qu2oKQ z?q%C@Q0n)iE-w{Kv3>e*nSyH#=psdiay!U3?)GWs*|H60s$N8au~PJtqAv}yjjl&A z-ZtmYi7w}A9i+)&-cqV}Wm)BCtY*6b8x3&b`^X+&bE?#mV943z)hjX`Sfr~7@R`3$*??C%lCZVI<7!mSK)Y`mTEbnF z)!E^Rr9&vYTZDO!ks|=1PYtMJ$lfE~=r`7EDba3z$PKwJ zy5(GFUm}j;B^HdugF1Bkx!O$->X5$T?jXGYN+^poolzipO@bGI3Ca|+A9-4@xSKcB zTH;CvxtGxXH7gB%cJJwAmOQrs*`vL$Lr;9(;;zeM%Q10warNIfCaeLEp zP^@Az;DJ6=zsc#h_Z{G=cjw8gONHUz$Gtqkx&unO(q*Siy%tjn95ht2+pwR*BZ$|P zTb(??qz?;H>tIM@b=xjQIjUy7XRPWEmKUoZy$BrgRr7hG5Mq4oflHC!r=}APi-*0NU+xUs|%d#LU4f z;+u#wv&~>ov>u2)-#Y^xvGU0Ksh9Di#q;AmAoNfCwH<*E=&xDM5zZ`LkU?M2kH_Gt zeSY>jDhsu10@-ubzAy!C?tbULPffDUHHl8bHc$9EJ-SPM5nX)q#WF#CG44WK8PmQq zijpQ7>Bn|G%^UzsYa6 c?`#8b7xQkDo+a*l#bRP+##TlZhOYno2WxfhU;qFB literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-odc.png b/system/images/media/thumb-odc.png new file mode 100644 index 0000000000000000000000000000000000000000..2d31147a848639f071fe7a9216cc8a01415def33 GIT binary patch literal 3239 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-odf.png b/system/images/media/thumb-odf.png new file mode 100644 index 0000000000000000000000000000000000000000..5361e05743b80264eafa9a9ac063f324e0097ce5 GIT binary patch literal 2496 zcmbuA{XY{39LLukQO*e$Ct^js96el)3q9Nv=29!%C{L$cQ=W=E$GYiqxn!l1RIbv4 zhjdD6u`!REvDq;8Xl!g_&tp$)JNGx-=k@x&KCjRBm+ueX&+GNRapHKW<;E==0RVty z82Cs80I&}E-cuLW2M;L?=BM7yzo?Yc z_?`!h$!p`I$%Out;lbA&=D>nLy}B|g5sgd5Iim!=YPn)^3^m7n#GKOU^$fY})il#! zG-&m!y+ZyoHvJT?=N*?rm&-ozW(vr|`Q+ij1;Ol+vYj^@hwuG$sIQ7P0U7B2I5+!* zNt=>MsWM3ehczNvM2wUEm>9i2F&f;5#_;EgDI+sWN|Jc-&k1tgD4~l#w_?!0ot=mhc_NLvmCL~j=EM3h@G)XFoqA%R zyJ?1Xk4|mmu+V%S6ojKTF#B%*oM^3^dSYMx6J zFBX!AR}F@nla$mEoKUU8EebP<_-xV;cS(7CpzFmnL#EZLR#zk%&B=l8IuJ=#S70$-uir4w-%iFn+i068_@G0&@IOeGBUHO{bxMGM`6xxZO7 zvPAThMXzB@(&W<1qeJCXN=P62tDM(aKZ5e_g$OnLEMn)bp1nawPQU=_aqlI^yVQ0 ziqU!z_g~S1W;Mz&x~Zp}mg*RL6+%i2Lhf_^07V9sLVx)DMSN|owVQf5GhXeJ6BPg6S*%V=mXIH_1i1Ig0f z#a%A;ygQy)O0$^ZfR<#a?#Y^-dpGU7!kLWADy*0 zc=y=UeJ6Nq-Wwn}v9y(If=W4;#Wg#W;5UQeh~XeKu*`z3I~{<_!B-ez+M67~#ZWX* zoQQ`nrV4>rA+lo#w*PKXTFee`+qQi*)1KhEScIb^duN>@H*0{>u#E8DxW3M|o@9ni z>K|8l#v<-r!6YCv*O$UJQ1}%bXhX>S70*oL>j}uHhMV%;y|`&q^@a7Q zK(*a{=+}GwQN>5s?t1vcE@dM`o9anao@(TL@^M*$Quz>cd>&(W+8_7m9V5U;bd;z) zKLW(M@DG^?eOyt+p%+Bv;Y|+736ibBZ=C8qP{o8THMVh3c*i-U92NZ<0SqIS`x{P=8 zkCSL%Z7Ji1D`=PIWEQv1tFF~YZQc=q^wGMcr>y-VgZhu;-G;nfBMf|_9S_gU9HWDJ z;M;u&?k3SE=9yCEEL@W1o-XucUbTZp$5N3=X!m^cq9cHf>?sHUO7Mc-iT?w1b1<{(ip6#e@_WvU-FHpS4+mNuL0nVuys724t(^0> z@xxxkH^5@37FiHCFuiudes3}3{2QP$&;%dwtwUnYHppea9XKKzjPx$Dg2v)d4ld#b z>*TCZH!!hX88nV3#jByLF6AmdlpL;(5`n^I!0OU%@0-W-i|A~?vkFit$J7a zLu(%AB^Y^z^rK>l-HsZ2?r!4H>ZP9C#B(ik*%^zIbF)06%@wn*rg@R(6l0v|L2Ix@ zLE%X(Go+Mx86wX)1G-b@)&ck5TeL0Ix#pC{U!?$6B}^L(lP|0F>k;*a@Pi)%I=|Um z`T1-dt)gpd^fv`uO%tm^uW72XRd1xgbhZbMy0eW`fny#!@KP8FA%y-<@&EuD7iDHn We&WyP4G_YA2n!27exxPnSH!=R(@G@( literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-odg.png b/system/images/media/thumb-odg.png new file mode 100644 index 0000000000000000000000000000000000000000..a9c7603cb36ed84be552a5691386a763c38a8d93 GIT binary patch literal 3069 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-odp.png b/system/images/media/thumb-odp.png new file mode 100644 index 0000000000000000000000000000000000000000..d18d2c3320988ffbcb5c44edfed23343cf7ab6a0 GIT binary patch literal 2871 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 OEbdyF!v6gG%l`la#=#^2 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-odt.png b/system/images/media/thumb-odt.png new file mode 100644 index 0000000000000000000000000000000000000000..752ecc02650cb3ffce599586af9cd1d1781ab834 GIT binary patch literal 2414 zcmbuA`8U)HAIBw2sC22cG8MNhSyHKs>y9K^$dVR?YNW{W*lWhE#zi;VH!Vn-dO~E& zG9+7;F(ZR!?2LUdmKla&W|;B0?!WLn?{hxqb6)SC-{-vkzG!o1ll(4u2?>c!)>anw z5)$i>e{aM3wWjoD!|K|T@<3leOGp&ND~N7NukpG7`!nVec`bV;B_t>s-f18tSe= z$4j6-09b{=eCYoSQ$JuWZLzSj1bu1H5(f1yP=5nf#ZZ48>YSlH9vB%gQ3t|#Ah`j_ z73POvr~n!;uqp!aGPK13vl6CTVPOR3`heXI-6_DIhS_fD{Qy%`5OSgA5iCu?FcCz2 z=zIl}jj%KcEHaE$!9+c9I)D}lGc=g_3B$!8UIG3LtnguR96AzUaRQj-AmBhV7RGCU z-3BX*K=uIgJ!lF5(oOjJ7c|_4>1OEr2qPsRV8ci$H2MIG1cQ07!h;?HG~=N8A@pYf z`3|(j!2BRkLqH^io>ZVmL+5MY_QMZ<5X{2Qa2WamA^|kG!}03l@ff zavy$1K*tLh$N~B@U{=874`{_h_Z#@}9~k`#6i*oY4voG*z6*`s(3K24CNMq$Eec4t zpeqp=nZRWL?J10W1x^<<1p+k`y5GWV53uPlS_X4H&=d^q&ta$#S|7t$H89d)xCrJL zz-L2uDs;Yt`C$+Xfzt`RF%ZtdKsJom!uWTXYJz1R%=N-_3(R%_s{sabp+6I-!9f2T zdfox$9xTs8?|bO|FHpRoD+v}^&=drdl(uT8QV9u}Icp2FV`wkC-y>ve*Sg2U`z)?X zDkc13b@a)W%7}sN$sOkn8XniU>33`m$KEGb@_7ST-$(jabtD5m%}~EgsNW<^EL38T z@=q6Kmm8kix^8WU|9iee!C=^t0MSCZtnP*CFSwZsU!Bm;Z3!ZN;Nd>~((Nbst%WojJR3Dbp3ZQbuC%iojiz!FNdiR+We5cuB`JC`}&)wu= z1v<-A=vGiiIFTWT_9T*Jq#GA|Ki4Yg=kHv@=~O&s_sMVd@-giDmPl`YiF|^)dznTO zy)OnuoJ^6jF8w;Q3)eodMnXlogz?GwXunELw5Mw_%8H(qla9l1mJeTz)-PM@w56P3fbm_`xkrGE-J){?%JJ?(7itX=`#y zH4)OOu9?`5i2B`Zk9&1Xm4t3j#w9IEb8}(3=4lz}vbI9Mu+DeQf;X76Ckk@|kwa51 zgbjsGh;hX1X46lFmK>HsWFALHO}*FPfF08g?PI+CZRf$%7FFy6-!BQamp3TIo_kyJ zZVR(SgQz5n+rPtvcoxazfBZthpnZhXX>JZCkg_nRkfVGFZ!vpb-bh=zY|N{M&LEiK zZ2a)fj+$1y=DC1WROxxmMI-c9@sUvH{OxHW4=-W}s8g$3%^0S!4T=g*^N#5#+LIyo zRTO1nv$R4$Che~QxAmAiM3l;cO<3+8W+|z4?9?5ShfdCR_1}^Mro6^b+9PMmKX7BJ zNggw~JD7fR*}@ocKo_O_FtNzPg8l8R=k>DlA6CEt=yT48< z3v1MUT+=^xg(i4A+JU#oE;8Fw?1x+)k;PU~-inirtJ3l3qZLQ}WvIa}K z_PCZmNT__WZ0nXZ&OwckLi^yNWi6QpUuraTa(6PH2x@e7k4%K(S`^vmo+IR5*e7tRn- zHBkdJWvQJ?xPC`x#7v}??O#RLQe~#nNU*o~3UYGzQpw)3x{!$*^4Co$FJ+9#L_dTj zR`OC#-E1Q^t$c+qjtCLiY*C*MaPn@>m??0s>!XdR#XXZLJ5o^Jzacl|T%XV_XsIem z7$VlImA!3pU4!ysqIM<7QqDiBrhe0%{5vt%cf`%aYf0^V5qh?lW`!6B=51DCMP+&9 zPH@gKT2{@4DC18WzR@lyD=N2M{=!4klb^A{x4K6%F-TLmp}mj8;9fZ;ei3v3ri~4p zV>7!9w1pcZ6R`{7xZmb&JeGZPY_V~tay?Q|PvqJhI`1_l{Gv7(L+4TpOOI!mY98Y5 zzQ`Un9E_EcGkwK9N*|W(vPn%{WK}cAXs_^$r&8A6oc`SBplh6~gkd|O={big5swrl zCbn-snjSc#SBm(iiR*OLFmC4RR+0Ac8aJLVS47d#uj8Vn8hME$ikz@2ZBKJ#_+0M+ zw0{n^qT4*~cZ4fBhUcZ~F5M>jAS{xricll@dD*rrzgo3Y^Q@3a%jUbqNMhcdR$W!h zQe;uYqm0*^=Vuo>mn$MnLQl*VyXsRIo0`6z69;dgru5Ml+AGv13F7>}#A~;1vC| nTFIt&C$|58a#_=oC|;;S#hnR_F}QN@UrSk^wz0@Fcfzl41rSF=KBG zV;jaG+gN9;*~T)@^UU)+^Lfwt{)O*8=Q{VfuIrrphx^?3IakyTOA~QXc~K!DA#pR) z%QuCDHYNOv9l{$*xpO1B(YCl>u(%*3R2(k`J8#|KoBVE?Tofv#E6xZBZML~_&Dz+c zg0}>q04zXIL0Bu8H^4Wo;0Xi*DhH0HgW12Kj2;L=1PeSAgaz)u08BN|zkr~qplK2| zBk{N(I=8;T1nYc3#VFsg7Pwaj+^q(Nmk>T8SO!stT7W(S!>C{tLO=9EAuXV5J&@A} z-LB$Awu7a^e7AbwVFNI=iuTSUzEprb#-HFKpMFDa)9{^Y-WnghO5!DVLI2bNmSo;_ zGLOQ6Eyz4`67Smp-;)A-?1i4wK*AuuZvnBd0do8KF?6tN4jEcP{2RgPHME`$$8|zB zRlM#$NarkKUBw$+MjlatYh)gK1+}dKcznUlN?!Fi{Fn;3)B`IJ8rTfps^lefLQfij z+6maIl9$#Ez3qa08i3DzP~SWfMF+hoKtK~XvxW{WBF$59A_E#*Mq)d_tX}9B8-Bxp z>Ly^u90DT(4+`*m9{JM84{rn4AoR~VI?qF$>wuple7{CuWEp9kgeSOY)i{i$00l$* z)@eAX85~$d;yWSQ6wFycGkc)0HqfaSc+mnf{~)2Q;LBFfhYFO9@E`mFI%eUwOz1m{ z-#!D^Ou+Z+fcM=HV-~^D!1Qh?e}M1b1h&n<-VFe235jS2U$udxQGUT7|8)l#(FS(R zB4b>{t_B!fL{gd1LBYv+HPLC&XDri&^}XF6e7NzvmA!&P9lWe0)2&2%yhs zV9PYzz=5fg@H-~NoI{crP~i~2atz**z}qP#BobkE`GWPcfq541g$z}`Y1u*|gPP=M zmMB%HnqzcPOjXyIyhBA(#3CU-dqToiYYjTPjjc9=mhkdCuX|a%UL}u3>kd^&4*3 ztYe?kfileCyW=Y{3sEptfqg~t9hRvbHK1a`yt_i#=|oulTwE0KP>&v;;I!0})24lK zc&zJLv9V`b@zdO+?fxedvdx?|ZOK852b=fZKK{_8ct*3gbzH>b5+~|{6|OSo@sNLs zR`zKE)ElRh&?-t0M@Bm1OICF#+JP3`76HfOxK&ZIu_X8AX>84mq`{PU&qd?h|%3)c*WOYb>wqx~oH4q36@MZ_j({KlNnxdSm`REfxx(VdM}( zV7f-2)h+)U-NYx49;HZsR&(!mIV4qUq?;dnC$8daUf|8Jy|FeuXH!im8Fwu(I8}vE z0MtL_!&D*2(xUg1AA0!4AOourg= zU02PJjD$hzOg4Q0Ov9hvI`O`AccfESUQm`J$QQk$6~@wXtQsU4oIgWESy>}{6~k;K z^YMw$l2J{YeQAX&`2KMGzTJ7Z4H>Qelj+hn^{Kht6>pmo&Tay4ICvdP|7cyXQ__VP z6mux;OE}qK-gWI{Y)Z^)g~Do8xxtY6AqlQR|3#9|T+7)PUlc9Q$H~!Bs3mt!QAqex zgfj=4&DcH0jWXlmW^@k2PWj0874Z~(7xVqGN|94#jyE=&tr1)iU#AuY6tq6^g7t$& zGIC5kM{~TNrzqgJj&C2w#iU#g$<6cJefrimbY&ut8qXt_yOn5pIvV_9e#(q>RUX#D z$$N6IwBM+@oI1d}$HXjfmc#!LDbeLo5{G);sY>}Dz!a$+Ymo_j7)Exxd1BO2+1CY#vW-PiYl?K#6Dv>GJkRRIewu_=pImK`_%kc zjMfMJclh!nfRbU%&}hdb4)fZmia9<_tgT>Yem7{bZ@ZrbyqQud?^DU$KI0OqAu$6L z5=8Vg6Z{A#)JwzjtTS{JZ_5s`r$mH7@9)5_OZ`USV^C!O!);uLC9IB}mkQm@!E~=Fo|8R) zK3MrcWg7knih331f|nj8u|$M%`gh^v`ThH9F0rP&>i4YxzA@edRI0>=V@8bGE9TQ< ze|#Nqh}(8kgMO{wi|J_}5iLU#mL*^)={!Tn@fRN#RI1HjkiB&B-o7SA7aMp z4HMGE4x!W2AAj5ZRoGY7pKEu@(x=2|K}7a|?$f)6$yU8iisEd|;^VHR7O@pe24g2& zK66GcUNMv;b+j9i8%W;oy&D)n)2mP~aalvJqw2>_X=t7u@Zh9Nbh^E#I;4pR@4N3G z^Rb5F`%Kf^epqkhUQ*TQU-;eIN~p8k#Mij8-|d!I|0VN<@;)|kS^M;D<#940toW=( zfPO(`ta{3s#kj><@E&0uAWo1K26-)DT zG|enIJK|MYntZWumvkw`cTM#nE`{YMU1;Z{=t33EX@+%vuT#UhImy@9MGFTmUsaLL zcwq3VK_tiC{t-_*MuLz%nW}r?tN*oCBOl{#!(-{}B$v?03q^)^n9fmpZ(h_5+Rui3 z?Rogvz0P#}d_H+$!yS9-;r#^f&r!L9JNZ)Z$}coW-I(dML(@=Mo~@*9rOchplFQa7 z)~lykv_Gz8rth1|JBT$7LRX?Tdn8pe-b_nipH)0&Ac?mF!hH-o-*m`{NyK>x?}IEQ zKC~RtC;Fg)HEeU9e!&Vi&TwVs4Cl)GV6)Ab)!3l-D_3KyO-r?laNO{;e_f8EG|3i~dKg}iC&Z|x7 z7sRBiGoY7}#4}yK_Q8ENm2&blJnoPZ7UP)xVw<&}3tlD&Xfy-2EwIe|i*nO$noIss z40$tR6gV74j>wn^-+s?5GrC2B;A=O^@lK%!*s|t!#->!dU;Hw|aGT{YqBg7Dcy5B@ z6}(Wp2R7DQV%?qL2o3sG<#6#trMG0U6w-)Cu$_GQ!CGF)F4L6fcCb}{-|YAGIkklm zpNhl7m*jM-=UfjZh^{dfuHx5{CUw1QMHXnWYj?X^LbMe^_k5F_^hs9g#SUDr97HTz z2QmM!5(snEUR!+0Ho=l$PW&-MSc{*c1bIwtUbbxe?nSFrKzEr|866vD+B+3IxtnVe z`C<}ZM9}-Bdw=CeU(@UOZ_&~;_I3Yw^*d)ZCe%1(9Yb3(-%hm`#z6rxVT%E{&yuQw zcAa_sjXl-4=^k1Ta_d=VbA9n|B-U1jM?S?UxxYL z&1hbmk;jM(GkLAe&-a|)zwo{1+;h+UoO?g_+<0!o6@AEa&(-uIIFD?iO2sS=5dj2@j*_O}e z2feima@GLbmsB?>5>MF~_+?>MQ-%L+VG@((gxJJg<=*I8gi$CYv@z~;=bjat#&X(q27l+T$ z5#KO%)02#yot@r}Hh6y4(&EDJ_4O!UH$+iEw4cZDAmw{M3Fz~_tRM&I;|k48#i6TN zzZT;{K790km;5o9K01s+Aq&%ze=z7pIceF6F;#Hr!aNgKTixCE?MnuDg|#$Er{#eY zxLj^#d{kmY;O9>O_R7l7Ip#+%m$aCVdEoe;%-MuczjY3W(14B${xCr5L6sF1rYBbx z71n$$Nr?&rM}{^aD^eoE&=sYh0zAKy`^vxMaoFtK7=EKu|ig}g97x5i#YzzWt@ zSC?5!1!+kw*aik|bdcOTHuSxR*g7=OPi)4uVCyTO5JF>JQ(X;hmg&ZH^TLp(zMv9zAq%E4vS8&XU*<*=xY8AL|p(z$Awq+n}YIVrRQ zJ+w1u8Kr&0LV3f?jc#D-4=H+d9Sxm*QSSi_KlWg0Q$r@;jvZP&brxgR8h1t^LnfR- zc7yhrnA?gZ(NE9J+>If;aXqrIgrbtJU=0Xs&t*eQ<&2?(K#to*mB1Qfe1hJC^u>VZ z4%hgUTj9{nhQ-6qS!x_-36J@l0<``>=J`L-r3P;H0}x8V=0>U*V5&_+ee)I`TY0Z% zoNaxra*(I3*5(FPA%$mkj;g`~V;P=?Vh4g0pDzU%MUIROvceyK;wn4V?G3tJ&vT#z|MG zp4ie;o%`B1*FOW+Mh9CQFWbn@^-X?$9l*Vok9E()_n%75Zt>@ZfxTK_Qy!39O)%Yf7fYD8ee5xGIrAsFjjjrcpZ%+H!FbM zAYp8wRPF@5JdHPB!weeuk>Kv#)l7N9&g--O!p%Bxf1K1qZwt^oq%&c;QaCr7cBTJ# z|5pgR4pV^L`qOP>mp<4t{vVDC|$W!y<~d=6;dN zY}8*_u$DFFIX9mTW6P7bq-+oKuG{;1QM=;kj9%;ph^V+2J;U_1+0^!RiV~iTu6prH z12u?$R%!MwM5_OF?$#NHArFc_!8~^2kUw#wS}UzlklP+7jiW*osb1`KED;lPI+S|) zWGmkYR{IMTC-TOkYjXOXkHxUJ$O+z+jH!rXL3Yul^QJ&|%RoMzLa1T_ndZdm#}SQ} z^%KkT6U#KL6TAZJV?Em2wDE!f%F_F#rY{-6&ff6wAYCK2dIP z32I*Xs0Bk-2cz*A8)ye(pMyjP$=1a`ukqWo4~>2t3@m={^(gG(lT@rSfm4(8lazbT zDj&h>UK5WC@@@v`2oLHoax@g_2=j)q6ISvE?z*-e)kze33MzY;8G}-G+Fznx-DqO> z=glZEZqvB*-?pmoTT z`{0SwT}u8gW_b5B$I1H&63O=B0g^BPA#|^<zI3UHhgt#{(oA>eMPU zk4jr>aD&YW-RIb5ExkadQ~nzVvqqqXOo3Zc*~3Ir+Dw$R1gS?sRQ|GbB<=l}5ShG|yZ@XEd`>=f<~PXw#apbfQ{Fi( zZ5xDB-VPVPeq>}Ushi`UoXZ^6K53H^aYQHiAvMtSxB1hqjKh`6ozj`sH%KAm!LKC8 z+i7kMgk|MA;AmgY;7sz?6tXf|iFfYFdl)176;8GQ2@oaximbO>f+KI#=N(x`Fz)1b za~#IjULli}rK*w=M*Ij`)#Lz~Qge*(G2}^C-72l0y~~>i;b-rY%l%eGOSk1MM>BV5 zQ2#s?x~9BQ(%LNvv6Okrtx( zpr3b5;>>O>bpF*IgnlG_P>g5%4tHr}1V!zsET}lOH#m8&M!vS@U+uRJ#46ofm@~^V z$gu}oFLc@p-B#6@Mo~KKHIKAgeP-PQ_m9sLp3-K^4eYdyj*UD_{9{uff_Fah`?X}p z^m8vC9nwhVjm=fx`ynf^9b~BF1dJ4aG=e>#2NdUVn)*B++!%ROUTc9>ve*QNJWv6O zcj(bTm5R%|wsK+Ty6|#&>WpRC&Jb3Let_I}Z1^Bg7la0i-!3fhlcTzr@*vY8g$JC( zPj+Hh5sPkr706v}nhQJBx1~g>C^eEuJCfx(g+% zo$?R#UhP^Y7ViHEA5=^tnU>mJ`Zo>v@tPSr_o7vY)|_gw#TmK_;sc$bM^8w z*C6Ily2(BNgB4`|?Ul=_e?Crna)^eyr*0xf)m<%2d$9i;`0u{M|1(p$TDiIxELxLV Q?*EJEKQ_@pYQKv7ALOHBDF6Tf literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-pct.png b/system/images/media/thumb-pct.png new file mode 100644 index 0000000000000000000000000000000000000000..54cc3aea193f9755377a8b44c15a86ab4b2daa2b GIT binary patch literal 2248 zcmbtU`BxH%7N(Ni#%#=SUa84c=Dd84r|h)2RP^$Rl`WbzTI!fKF#nFP(?T60|2{SZ(YCRoRb_$ z>uAK}C3ELzu=yzpg?xp-sL^Pe37GoIVn$zQPD<2Q_C!CWMXgpBWh8WwiMZnQf{$-= zQ=(eyD^OX9VAL zM@z+Pjn!zuGH+{hW0}WMDV2GtF?sM9gM`x$Tg)2)9coeO@fyeo}DqLX>^)^&}maK_5bJLX8 zI=M`yQYqI~1)Mqd%+#1jz@KH0Z>&oRpmUN+3*a5i)xzbalHB(l zO?dv|0s))5xbU^Nv$>`mS&J)NUgFNrPF10^c5q^p-bBEB&Pr^mE@4lMpt6(dDvIen z9jhWiZx^XB4K_C1-$^1)G8wfvT%Sz z`gN(8&*MM4f9K%aA)favuh#&z9hT7v1*XkCY6Xs8NIow(WOW@ zv8;eO)JN}WXN?WjRup!U2rEn6sqrsujaB7EX;mdzRp?BYyn{ypfW79fSHO3|Swi+! zoGI75U9A1zBYz*5dTs`_k4GdV2n-Ge?l%JI{)9+q*_z2X*!T0TqF0x+QIB33F1a$7CXYs32XiEvgPiWGKK*J5Ti$i;{V(A( z3U<+UObEApeV{2m ziNJr`KuU;8hq}z=Xr*L#ureG4Dgl<=p)$h*>QEC#KSp0eNMLvBpHvZt$Xw^F?2~@q zcyiQeu*$vPL30^$P8luplb5}!T^Oq$Hnb@xT|IZS%S5-SLnzx$nlM1U9fp+7u` z1EYck{2$P@pieRUBe-&>?=dzO9$ZWv%vkSKNayX9U!oN<3YH_}acW0BBNQuBZKQdU zpE)E<{d$lBMP|NKJ9d}n!_|&+d)pq}}gx-Mkf{4w( zO&Psr+aZkm0l(Xa;XrQMy5?so0aoyH@vlROC}@mrT#Ek45f7~(d&rWjF9AL?TqxpM zcsgulBXilFV-%tTgnu%j##`_1V7eAC=LVVk8>{n$i5{DKif z?XlBe@qu@IU&zI5t;xG99b?p*n|j012##~$l3hxQk9ZRsd|~2}qcRGdYbbG7gS%C| z2Lf*hUGhu}Y)#NIP>h&81ifxS&BIqpA6wH>PkZiIvJWy;&qJkS;ZiwK{U7i$(944H zI=ZFKAwsw5G9nCk-WqADJQv_b-pBw!@?gp{he?k^rSxzq7|b$8zkK7knbIMQ!Xf94 z&?Vq~ma9)K%-G0$py&YF25gUay$RYUEc(GSbru-pk2q>)S9r<5;u3rRv)P{^*74UP zBj#&VG&f;itTIybwL6WYuU!DkFh?Uk{1v7|_1-gqfoR7djG+STv8)Q67uR7Ghgzi` zVgqZf^CsX}LgY4m-10|_&WrD01I@Cf0#Z7T1q_NqB)a(SGEQ5W#OM!;K>G6mZF|I( zQmvqu2yFT_BSv|wDj!s7C`lZ6a{)`;lc*IGfN=5)H0-_!ckwM#gx(8F9<_m_gQyIVRPI?NUNw~bYQ!1H9ZA$P z)E^E+RFmHYyJj7yedOu5$0D@TL0hBQs$++pp4EZ4+Fog~Kh?@jD;?6Fxm;X@v;e`K zc6=}=iaGOeI{iD};}C0@QjS@_)_9g@h?yNLn6lnc65iNd%$-a>8`htERn3IMGR!z~ z=Nu0|MGe#din({dCeI2I3`>wrrhm-BI~b)r=~urJ6vl&W^wLzWZ#552zfl%qqE(Jw z#JQP#KZ(ytA4De5%f6oFrSM@0n+Voh7&Qpe4@;oYL)KZJRA7Y3UfqU)r)ycKvHHWq zpt+Kh20mtFd!1nxXs+0q5@j}FtQGVc5jy?;vy(`EN@rLO@|Qq?c&2C{toA4*`&I9| z-C29vydT?uuj&|e23tc%`Qq+;5JUk>7$TJ1O+BlpNh<|e0(Ix!>9!4AJhRFo6>pzF zo$cWqIBMGWtCWk3=ll0+2P;UXUJqlH&}sASS+=UP^QM)+F@`o)wh$L735k&kzW*n$ e_uFjI70uqFdCOMsihtF8%Fs z5d{aJC@2~rj1a<`$p|BZO$fYXK@t)O86Q30pU~%=d!BpmIrpdMIrq=wK}RgiY|Rh| zgoQuqa0mji1pEifmn}M9qA2>sWpoJ$1Q3Xixhu6%#*2DMLdX$c#Cz5T7=c*oe>~u% zpGR{o$m#yGx3g?yfGv~MPYAd(GK0Z@?qcG)+e?S|&-%GtQc30L5N~EWpVzC?>t`1h ziuyT+nrpRs{ZC}#htZ*`vEg7!Qw)PLKC2v3$XOE6lv;JTg(#R+&S|wiq`DV_JlXu5 zC#lZ2rT!e1)Gd?9H5#F6Hol$4nU-Cox02zB1-$GLV1BCH0SgooU8H7ZW z|6#M5CdZGE8!j;@??wg*!m;C&rpAf!PN^8v#dgi;Nr{dbFXCP80o7FZoStA%N;P>6`={VNfpj^=0?ddET zVL2B&+T|D)2ke}1XZkiOP zbu!M=$VXcnexZ^5$qgA@OfOR1jSe~q77i=qvCOuQNvNCRzT6fs-jb zNxwSz+qNZ5Gv84vmVEzw`Ken!EcHhz2lu?X*hD#h!ra7MO6>dk@P*!napGdHO}xP)p=<2`iT@e(k>)WTTx++0-1V`rWBbhVj^33|e=BIJ=QT zn#zjn{L}hOjg13TQj!lVvSgWwAG*f2_NL7ho^nsdb3;U~yDL)HQSYB{M?d55le{IF zdR&^DvW^2-1xaczw4J#;^3g7Mjo`;C`+v{2bSoRjwmn@Ftwh;GI6xf#u>zI}_P!I> zkbVN|NF7zBV(%BF*4nPfb5z^MUONgp5)ys@KW54;ZO!r=y<`WrqN>i9BQN3!PJ3+} zVN9(DkT@!}UkP&tf$b;4UwxHZRgTXyd?acG)Yd&lT@~p6h ziq9rE$AN2sE2>^10Gq&heB&=4<0f;R>7J_%zwJfIb8G>pAb*(i$EoCk&s&P2B&Bejepn#hBH7)l7%*~&AP@c?|YfzwbHo&SER$@--6Qts9l#0%gG zuBlA4>D(D|dUx75TV&vyT}Rwc71LyBXGKoH9?_dF?b*B@Y*&Fec}8>}4b|F%be za=!#Nh(d6?Esej4zgBu%<_XwCh1JiXwSsvjoD2r~eN(l^OZ@5$G6Q}T>b5T!WU0{X zD=rSjO+aru$`ZYG2MXHRBraPQ-MnSHyaRE^On;i2h1#VULx{~B+ z3eE>p!U*I@SFIO(%q|ZlV8b(Gp1T55ZoBqFe7O18%ivH04|5vKEwsIBQRS^*Z7{rg zc9q}h1ZAGSd?t&7Th^|QLv8Q?*wX>jaLRF$Fv z=^rfYE^>whjBsmQg(i_QT@!Dr@Br+o(u3{Rg2dJc{^!1`{G6SJFzQH!OA6?boA2p; z1{}AR9q;*~#W+>FIH%Th05-C1)#o2NL$V#-lBB^s8zAx5FhybwmkGE)gOB4P0= zWhoJ7GeK7$uXw-JkA}*rRwWKzs@XS4Scjr!U=N$ZOl^YCdTU2_dDx;Vc#?WigFXqq zwU!++%X5Kt6;zfm;T*H78-DmBdo@lx>}GDGj>!S8xAsh^rx_7 zW5NjPRh!y39L)M_hL|zqX-p8-h(8Mh~_+0rDn3hvl4hB0x&Jo2P zz_}o^u3N|iy0$DN9ApC^1;|XomH|~qHk}^@5gC9a3 z!@qG2lp>w+<^X7dyUNET{1yU!|HTNfHJ9^*%`~>aEIzS$YL(IDZII=&F!Wd6UJq>l z{$E{0o(>rXQNx^i~m lc1H45i+@q~|Bq_%ngb~=(al3v*Y^D5`1=JNe(!r8`#(6y_l5uf literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-pdf.png b/system/images/media/thumb-pdf.png new file mode 100644 index 0000000000000000000000000000000000000000..5d816eeabfbb13597cef68b46b8af3a93531963f GIT binary patch literal 1823 zcmbu9`#02y8^`Up8P}D2yDIBbEV0M7%Wko<&PU2eZhNXt=^_kr8)Y)(nr6a|Y>m;P zl1iU0LrX3Z6H1uT3^vK#N(QyeeC9Lv`+V7dVc+LG=Xsvjc|X6s&qMhoG{Dq&qp_Zz zo@pTDzh6)9GwNCyu3IIoQGMFghKUD5Ks~*Nt0w9w?CSm*VSj+1Uftm4Pph;1m*9hY zc!$H)i;IeZ0SWz)7=|S^HNsMwPNx$V7YlzcRtyg-r>1l&m2zxMe6Lz7lj+oI-tl8P ztyWTBFKuhnN~OFbhj|f4Gzg**3bjflN+zpjW)MOeT0~I{4kC#O>V*Yq%TvW)e`!!u zy|kneiF6tbO8HIw@uR%IA1%n2GnwKC_q8gOfJhWoR3K?-f~-vbiR0+SJPj()N+bwA zg?BVUIWnSEC`8vQRPWy_M@Kb$zVLF9?8OTt6)$=8NHsSnZEfYBj8w0z$X~za$3#n? zJ(JYdC^#I|{JbD1TU2>d((pu7S;>z&i53(f$w_=%jI^mqTva8=%~5^$Ag;cPk}mS& zVg(uJ`O#7GSKTs3yH+koFOhl2j-rJH>g9i=&sxRsJ@w*(AUjLi*oY)03NGX#X=j9` zrK0k3&B_XLI+dT0Af#SZ%}k5$+|i1~lKMI{FAqsc7GAj`Dlb!xzLWR$%3gFKX9=>- zPT9+s>QA4fEiKCNaY0UwATv{Njwo$tK=61Ck0)a=q)m;o_IB0uwD4M~kX9lfrYqmQ zlhoECXHJVBJ(M;#^H0T#@5B6rQ~byiYA#pa+biqnkoWaT>gr@&T?#f^BM`_sJ7nG6 z3Jx3ntx!2Rp%@xEn8N#0B>VS2A#-dsrKA#iMI)Y|z9&+|-$?Z(ho zoN<`5rfKa!<~};tA#i7Bl4e(AM{V01@1L^HA$?m`bM;?@YmUu6;I5 z^;LiJV=L`$4_4|@sI}Ea_~*#2u=c4Sj(vCnF=(v9g}FWb;CWlC>a%sW1E5yG8kg{{ zlhpK-56rh%dMtHI4ianT4yX6IgNYj*iyove8MS0VoH5eXT{>0v{Ww&(E6h$CQKLR zk$Y<@BASxXmQ4L4#HM)DQ?S5#ee>7&rGc->!NH+?A5^pwy$A2M3CH<3lUu(PGQ2zs zO%Hzh)xfqfE~>=Qd(4pL19`r+w3nW~0c_dK@XR<|VnyN_S`wLsZqOHwm(T;H;9`pj z&4DGBZ(sUiSl`HJnYO3Qkmdl5oBR3)+c|P#mb7Ezgr>#xyThDH^q@2}|*fd$XH_Gi}Tc z&K`f63@Ycz@kX>CS@}H-UC0FK@l`Xvt1WH@cE1sjLtPNj>=#^cCTZWgq7T6ut5?f= zGpM~9@6uN8>$uc*AipP_Xhd^jE!<;gmhH-{VHx<9$9`^U$#f0E(yd|F1Ae0bWnN)J z8rcdZg)IV^wwBWW;y;zUs9~Gn#fJOHkAXQp9UZA$b@0J1-hZ1jWza1>Xu`FVaM{ z*y3|IIkyIwb?UTphDL!DkDIO5w{N?$CgtzVu=FqCq30DaVir5diW?j)%V`57rZmq(6B{t}B zz3A4+1NO%(Ft!vRa))keRBMtnRm4ApHzO58R9A@&CIJTAoXNoN*0>~d_#&w}cLOHj z4&YWu0!*!OZ+t+izVUFIi{Y>*xFKcM0+Ub)SOsa>#^Bzcq2*@heUW>q7V8^Nr4Ulv z*oN-Wpgu!+BI1cQJD+VRY`q`Ooc@ET)r6OO%t;LtaQ_dO)+!N|w=Xa9yf6k_(JOBUy literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-pif.png b/system/images/media/thumb-pif.png new file mode 100644 index 0000000000000000000000000000000000000000..6c702557b4e845836626e4440a2dfd1c89c408c2 GIT binary patch literal 1103 zcmbtS{ZmwB6u#81;4)y6m5IR}OvW`V1Sr3TMC3TH`~{ww_q@;ZocElW_nae) zI27W-*}@?R!X+#;D3Tx?M61W?c}A%}-ezYmRst1H5yXvrXX|k`!yVEhLjnnja_bC( z_eC6x3dY@|knzx0h~Y}K#KLeN?I*!lPafe4nlDn`bLcY0oWl+z?c-R zpP=n?v~V%if-V~O_)wRjEgnmg=(&K7G+3YD;Q(|sQ03t6G2G9>*iC4zLY@p=H75ST zoDwZDFh7F)1fI-ec?xZDn7o7eJ{ZR!ON8Bu$Fo?_pfdv^)30Xj52xf_Sbr zEQpHwM)N=;i*%=mV3p6h4YkBEeM#CLR*JcFGteN)9%>l-Bs#{iBgFR4tBA_*_^I1<-fK0ZF$QT(9zwFoDfrGI~tA}Q@a z{ui|MaKoU__Rv5oIxoQB=2cFf`8Fk)5*5;Vb7B!0#%|x>;w+95SE%J;u*n%GIVxQK6NOL^2b$1AQ0 zHaf_)+g-_H3%BZn_ZGkCby`##lTQ|8*5yctn3!=_iFQ@IdWs(+l2fiZQWI04yJ@ph z!xFm3zs}v2Ui6Y*XsQp_R=ra$yu{(vRqmI|3TpduZqZS_zXe~UqF3?@k!vwrCVl8S zQ@AfzAV{)J#wb|o8nSQ4@_Od?=7UT_0zO`CPgd6z(wjXaXNBzut*Xu;zs%e;!lS)5 z#ha7=%p*8SHSm-%j$8w4555y-N5s`Qa@W7(X|>e*W%wFyIXuJ8 dJWaKy-f>8MyK;VEdq}~m5EgtWND}xd{Xfd=E?xit literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-pkg.png b/system/images/media/thumb-pkg.png new file mode 100644 index 0000000000000000000000000000000000000000..521fa1bc760254576a92fa295cc0aff093935bce GIT binary patch literal 2660 zcmcJQ=Tj316UI>^9R$3SQwRi%2qK4qiXJsc7f=y71<^y%93UtlMFG)7rJP9UB|zvs zfj~kFMSAExKtd-Wv?LH9lt6g7dH;snnVp^IH#^TfA9iNL?pd3P37-?@g*(Y zowdKuZKy=&r9`AeeoT$}w7av-+1jkd70k~}92^`Ve*_&I>~C*#j_5jzQIeZ9(A`>; z5tAAJEhFx0K^mf_EDsqOl$98Uih=VE4t95Ul79pqd88nM_jY&CS#isYwDzBsIm!RD z)Zvgx&1S4DQbq@~pyr>uUpDEySkkpeIgH5N-?CtJ0)?n$h*>qSQBegbL|@rC%}^d>@C+t12L@<>NBVRmY~C_Q?lzq7BS zsk|_4VSa`Sej?4sCs1f^1hMreGbZW5zZmmdbCAcIMB*cX(opqiBGah-Xowkqx>O~q8 zyahGV)2|eOd>)dcTziI8rs!Hx72+^(ilrj4^A_2uA`z+?l)%zCW36D;^ozZacuWPi#7Oqd+mrPKr z(>rL1$feKWJ?kM}c5Z%YFO!JCTxcM9;rxT-!y0Ts?pu$7kKR7h>8CV|>gsN-RDHDZ z8_+akqREaSkIuB7+tNa`*E8dv?rUr++?kW6vZS|J(gneuy6}liUHG%HGb&pN6%_$E z+D^CE%kJ#7oBWcQ4|U6nwuRZz9PqU3if03-_iE+%mDNnHrvGbLD5LZFjxq|w`dfDp zednI^a7JGDyv9gmef7F%=10KZ$a$hIy4{^5w=$g3FmCd@9sjG=YeeCf7xZ+@GJL-- znR2BVqSsS>oO=~Yj1wX9M#L3)Z$o%?0jr_0{?!UNJXXZUTb4dUp=JcPTqiQv8i9`x z7e$8UiWs`MyDuI9PkThG`^2?e4`nNrwE>;`0$d~2fdLo19H`hVq@$*FfUD)RhGdy}2Rgm3pnXbv?KK2_@%k=Gd*1TWVAX*cXAM-&u;X z?x(yA2%<=7w$x@}S=4hdqzS&+)huog-^3N@dI&BlOvShBg9N3{IY)TmxdILdI2V61 z2AOP_yH?8bu!W83aRty7pj=?JQ(viL>8|G;j9O?Ex7JWO0L#1(s%~w2;zdPdN+%M8R!W89WcjLdZ9F0pkG}Ko8A()Z3 z%Ed4ciSfwr`vVheuo-wn+gJkvnr;Px0(J09=50>@QYff&e8FTH{;!FWd$=~$eulJ#i7VhA9_pwZ#5yoXhVtni{`~Y(`BKuNgggsfyNL4dpy2SClw*lf8c<9q3 z)+&OB5JdIwleASpW}T20yCbHFSHHl20UGNaUtGI`<~)r90Y~Iy@Jh(E)jxAFJ9Md+ z?Q0otT0M6U3IURidNO#DLp)E!y0S;RK^Zf88)Gfu+5aNTuDKed0x(xJ3&J1ec^D$Z z4n8^1w{NL0;FsM=XtFwr70Pi_YKyQ}-{W0&X}bZ-{w*7F_*Jjj1Wg;N{h;$ZM*`dm zdhS0{e!nQMJIfR;fr`9n77l4~|2%4fKAEBJa3bK(1xfG-=$o|Kya{^lyAVc}e!A;N zyHK>N#MK*X6ZsGI2@~;IUR`Z1i9G=GTcI49E5$@jA@{YOoy#86ZYsE8n>YTKn1+(o zAaIMfP2Be5iQKnxNzP-yMW94=$CF5M$&Tsm@I&nu_2Y*z;iO`^rksU7r-zq?j~V*l hZ}=a6y#HI9@c==(?^cSjc#D65xv{ko#_%uL{{R+JRe1ma literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-pl.png b/system/images/media/thumb-pl.png new file mode 100644 index 0000000000000000000000000000000000000000..840d716f1908108ffdb8024a80140510693bd7f1 GIT binary patch literal 1066 zcmbtSeNdAH7=NkZt#NRYNNNOcyB(d;MTbRdF7Bl*Hmu1l>P`*JGAD&}Qo`;)%8@9h z@-i(vJj05-A_YXR5g@4JV?)^3_-e2r$v*fJ*VKcR=lMO)@ARpN;u9XBW?hRz(fG?#n16-wB>GCxu^*0BEKK2n5q%}FRAJhH2@QPzVzLv9k1(Z!XBMp= zqDzj!a`e%#HoUA(m&LEFtDX2Zy*KaS&4m%4uZ8N2E`PRsjcHS<&g8A=&Ue|l9LIQ+=ubL)4s`7UP7M;(lbm##OrY-|4f>Rg($UVS>AuZguBamOn5g;^c1 z+xEARTLL$p9Pxg$f}gxk)p4Y!23Xn`MFw9YeMJ$reT8b84CVbn3e)H&MXEh8V<%nm zMuBhZoV>oKF1d72apNVC%rnP-rX;tlFn?DVZzmmPm)#Z4aFlT(wj-I+1u5f2^-(pE z(`$JL>7$#RnG*U2b?2BoCz)=gF5fquCyNWG&&XmFypX3Oh1c^Gl_A+N*90W&IkENOPlcM4vxF=1Uzb08&M^*etf zzo3OHY9RS#WjlV@lhXZ^lB>COd&vD(na=N#9NEk^R*@ItxS|gzuY%cr!tw&+o?%hh zoNO*z`5tYGq~bV6e%hO{qE2$VzL4~=)^BJGWoe@|JzZ4^k)%jTrUm7WTq#JW?W?xb zzLt|`Q#U%blS7Wcj4WFBwmyh^f>f}V4S9lGL3W*qj8$b`>i%B!s;21A#%zieDp>tL xvzm$g(xkk~e-hjc10&~E)e(>J5G$>t39jeX3iAoMB{E@|5DO)O>co!}{{h@77SjL# literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-png.png b/system/images/media/thumb-png.png new file mode 100644 index 0000000000000000000000000000000000000000..ce1f0a6b9887ab8080494f9cfcf6c71a1781f4cd GIT binary patch literal 2530 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-pps.png b/system/images/media/thumb-pps.png new file mode 100644 index 0000000000000000000000000000000000000000..5448204c87ce65924eb2ab6606ad58e1c8e56f9f GIT binary patch literal 2497 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-ppt.png b/system/images/media/thumb-ppt.png new file mode 100644 index 0000000000000000000000000000000000000000..bd649e1a55a15ca09705f919b1955bb1d1ae87e9 GIT binary patch literal 1573 zcmbu8X;V`N6ox}3E}1QH?SQuqg*@ywjnns4{LbcD5+0Y6@(PCGq_}Wctxk@A zLd{OBqEn=j#lG&=toN~!#d)=QU9DDAQsdeis%C@}$#LOIr9vi^WDuT<7w34x{lf!2 z6JsMo{oM<5A}Tp?a(q-SlTHbE(j_sM!*2OdKF%AeD=RFaBy%~Rn(Incsx|S#?Aq$; z+UnKmYId-El9^t@VRXbRRaESUw0chDK;e` zqMVw^EF?A7(6f{Ottcih%Vi^jy^MTfePywFeVxx8$V`kOC;d~y%*w@uiP4De$8DMwFmc+~g5+yBOu_9lbpIMSD%*_Z_mgNH8 za4HlvJvm;SlT?)Tj@{Wb)YsL~RFj*QkVk^D5@SejaP?KR{+_naN`-1|ZELc=ulrp30Vu}OFwB7%JkQCj+nw}*>L(me1N4Z52^v&nF_5hjy(gVcj&qK#Y&KyE#gLIy* z1+u+PVnFT%JO`~YAU)m;nRgPcz`D6Z0ay}1*xt?$xS)maIIus{m{*HscJJfQAAo6w8q0R=Ku zjN8ZEC%7A`n~LzD!rS)5>)!Ws0-bkwF8M~m2DQ~#2sufbR`&GUrS6}opC*K=YJYAi<<-kAo?VZ1kkSu=c-)f%Bs_!U zSP8@&naH#A!I3rpiu^v5p=b;S&xHsT-Z%iY!}v^PL@2DC!`BCGC8Q@7EY`2kIb6*s zU)~uTq~u`r>ll}y$wfQmsTGGB?1o1<;QVYa{ZK#3z0F^b+yR)Gx@ps?u8`%kN2xk@ zL0AXpVLYXT49N~VH}UZCxeM}HTR3zsitb5_w1e-h z{Z8}aAls3{rH4aWbKstv^LE#w9E^OwE_W!+W34>pZ+oum0%hvj?A4)LN*`+Oanb|- zZ-M2C8%C9s1-J~mQQy>Pqqk8d*Z7i$+q#X8DrF&%BZ4491G({*fN{R)M0LD{`w#Q8 BAkY8+ literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-pptx.png b/system/images/media/thumb-pptx.png new file mode 100644 index 0000000000000000000000000000000000000000..d351be8e23cdf63212e2f15521c3b51a18bf520d GIT binary patch literal 2560 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-psd.png b/system/images/media/thumb-psd.png new file mode 100644 index 0000000000000000000000000000000000000000..31fb6efa6eaa7b81dcf2d8d5b4595ff324dd32cb GIT binary patch literal 2613 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 SePa~zk7B5AN-ntO`0{^dm|}GR literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-pub.png b/system/images/media/thumb-pub.png new file mode 100644 index 0000000000000000000000000000000000000000..ce82b5c00a541b32952ac77ebf8bdc8575efabdf GIT binary patch literal 2137 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-ra.png b/system/images/media/thumb-ra.png new file mode 100644 index 0000000000000000000000000000000000000000..39eaffdc52d81c92219d5dc475e649ff4a44bcf3 GIT binary patch literal 2223 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-rar.png b/system/images/media/thumb-rar.png new file mode 100644 index 0000000000000000000000000000000000000000..37d0b3cc4c6a46abdf763380f30cbce37e149851 GIT binary patch literal 2695 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 N))sfnD{lPx{(k{}b>IL1 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-raw.png b/system/images/media/thumb-raw.png new file mode 100644 index 0000000000000000000000000000000000000000..2d26f299668705f43fdb70b622350ea5907b1cc4 GIT binary patch literal 3433 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 YuE(lOA>N44md}ot$LSOAj$Oh13!<`oZU6uP literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-rom.png b/system/images/media/thumb-rom.png new file mode 100644 index 0000000000000000000000000000000000000000..e1eeb34897200638d60b7ea9a9fafb196c8b5db8 GIT binary patch literal 3088 zcmcIm`!^Ge8&~_1M0|UbBwBB}QFNC=Q$$HOrKI#$a=#R*5Nmpo>y})S%b5E;%w}ws z%kJ)D*lf()Gs`e`F<X~ZPz~KCd#1QCZwkC?b6KIXbMK6T!sg)HH||-d$A{1e zxVrM3ZfYy4BDbNsuq^lM`uh5cX!%EBx?C>*o)9WtUHP8)Aw3~9BQdNTk-WCH)=B=^ zPHf78fpe1omPw`KTy}MF2K?)%#@gb%Z=W-v;XUny;XyiYba-_|lnDbb2>1iN?ad88 zhWfkud#JUg*;1*b6Asu*eb)i;*Vc-&p`*itT+YxWZ?xch9HWOuZmyR~BphbHXnCo*q54PB_l4i{5{bApCkb0u z`fGZks^A-ccDlYQU$`L1`xe6<>SGP`3g+kX(_=8zg_M?t5f-DRv3jttbAE1ijKf?M z{vM!rNTt%6lFSk~Y)vN1Pmdj>cT9|NmY0OoHf&y6G`R&+l$p>)ZCPCSJvBbsMQufv zWiJXBn2c^lFAY_Js42<%JwMk&YZEOmc98J+`bu0~SxGkRC#I^ei_%GM9_J1df1;-+ zd9B!*E=o&(H+7UfNcxHHq%B8{0XDP08B@joHG@cvBH~f4!#1W03R}-!v$DMRp2=sg zfVHgkNDEt)&gH+mdo)Jh*7mYMSsw(;*=f0H<}-P}vTfD%2bPLm0q;Z(x4JmAnftV5 zkrsOsbjzIkTer6i5-+^I{g+s0WJut?(kRexbP&aDWa3{e&_~9cS#s_3Ko%W-1B??B zIvaEqGn3v_VD!8g@g0xQ^=|TB+ghKOPbSnaWQVMT&e{t+*Dk5)8w(xSZ?NxD{Cf~t z(f;Ssw5$E>?u&SyuLbjp^iZMXJuuDwu?eSaa?G^SAQGDFYMz}DmnFTp%L}+(_ZH+u z@V(L-Ylb`VYXLjM~5z~MR?F$WNX*z#CwvM5!oc{J3N zzA&z7D#D!k4MMz?5^!i{BULRuaf&3mbpohj4x0{QofS=$oxZ2-0^`1qVqLN{g$#x_ z7}nfRKij_kK8h)5p9jK@gi!B!g)0htG2 zG|^!d-`!qAZ`v59k*HI4Sg8BmPZI!aKN=u5vMKd24Uy`%lCmlXj(VK-30ldP1H6pU zMqoec2l`)B^E@0F6w*Ng8_cpGyDyIMzJ>{8Ju_NM{OATN*KGrb(As0VWWXd3Y}y7& z4)ORd4@Q&K=Ixid{3+Ksdj+?$=bx@B2@eE<--LaG6gx;>j{4WEJ&=e!BzYz6^OiBW z#OkI2Bs^V3RKER;#&VM=cJn1>o2TFe1<8o+^`m8(e zj&u4mdY!%Tu3#nMUun6+uvdWKJB_8JE?)5m-*aWerq-TD zK7dqoAKu3_cpU1}f3B(31m^s;?vM>v>C{!}&+5cSUCO=@{+PftIOb}AF62fg?8rgC z{B5l&t{6r^+}R*6_ewgBc9*X{Y|JMuep?+#2q~a8@;bJ)sN~!Eoj=Z4dN+hDJMt3# zX+p#n@x;-MT26&i$KH}aQ16ip=P-5L!dF` z_3+?ll9|(3=Ta7f@($~*ZWVSg6-(aV!CgSv8I*&NfvZIi>9r+mCTtVtKFnDWNPb*y z?yS0Rijpyvv$@3F5FKjFa8CRHrc~06{&e6&WOk|t(1bYbQNPx05h|`L6#=y@9`8`i zjB5y@-6*VnIQ8nBeabIXT?<_U=)qN8(RB0;^yy59z{(0jyyypKDb z)8%rpoHn`xQfy2x3}b<{nsYzhkw@Duq8&PFjb+%cQK(BdqbP%j;16DK#!Ltbh0 zGI5)a()U9M0u+0JjyU8~9Yj37;a=n(+aaPJu0;hyIBgNi_S7x8p@eYUv!#ji8NcdP z4EZ3puDOf}g4v0;$7j7(Iz>M(4$_y8p>6{(?=4K7nW_shhE*}oMl(NdB-}LM?_2iOE;)Rp4n%t%`$*NvWZR0&!kyOp#K zo2@76k)xk;7Y%C`@|LnJax2QbZMRw--$9E?*`xckUWidK)F?>BmnMj=9^iFAX((3j zI|HpbD`*K4WDz@l{#ub|@#%aqO#TRHql6IdWt## z*AFz$>S%dK*O4uCswH+<`wYL>$%c2PmCTa`FSq7fjJ#F1cz*vIr0=|h_h{e*b2i&? zz%hwhpq~orTVV`oYq|lg;j#SASfb@o_3bJzko!c~>u?YUt$0_<11PDzo>D0-SGUv6 zYO(&ZrbpAW!`c_9D-T47A zJ7!|0?`(7E=KUa>nyd3eZEiX#?!cIg+A{MUnm#~+uyM~49d|0YMt;KShS9DSoy@r# zd$lKZ$(J2~6J znhH&za1Up4od>fP6)v);UeX9M6NgtTA47r?AHhQ!N)Jf;c$O&#VGLJ@?*1(S7kurP zC(DE9Dmr%BzDB#`T#MXGjz{iiM@;$zf9TIUrHJVAev;L$W}<-sNi6AUSU1U|yS%id z3jpn^wADYSLCBcje-OK=*hLSc+fINxp;=u@k-NxB@M}=QS2Hb;<9l7`uFvO=I{f$T zhRh}rw%`i*{Kx_5GT!l$K5WW0#in(;6~#Y7+$P$$J7xRzxV(~qmG?>wlFJfd>gZ<5 zYc*ryai{SQV~1P8(}*nrK+dY|G$c8s3U5I6HFdlBq8Cf~TQIS>k^?9)>tspwyxZ%n#m7g!7WA&G01=w&*~i{R{TFg@!%*%{7TDn~P;AA1d- n37~H6RQ?wqj{mRzxSq1EfQ{_K#{>g7|1j6AZ(3De{wv~t_qBK% literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-rpm.png b/system/images/media/thumb-rpm.png new file mode 100644 index 0000000000000000000000000000000000000000..093e21aadb617b42fe1de3fefce3150985239a7e GIT binary patch literal 2566 zcmbtV`8(SQ6W2x2%dT$MV#{|cs@=A%cCkvT-!4VB+g9mYJy5F}b;LG~I8sMYb(Bb| z8YQZ!yA*LJ5p~9qkhE?>5?5St1dSxT_HWpEo_Xf^%<-A|Vdk0m8`o?O?$g{SCntB% z&erOtoZKGFzp5<1>#1>T+uk(_Fz_!>POdy&=gl=+iDPVSqa8&@5z zkskM#g-fk8%IwtS`;4@#)TDRm*qxmn2E8@=WpZO(HKnqgfO|1EJlI68-4ctpx3_~I zIfp)e5DatK+1Y*`f>>Hyz@Skm@4v-gzsh8?P*2ydo12UCb5DI9t}ZV}z#mnW6efiu zhx_|d&|$q@9rJ?OFTKpRMrvv9Ti*Cs0RbNu{A6@!0FCe(_{@q4@SWmK6lP~+y)82}F&yrZ5)~Tl?>#!$pBxzy z9pIDp9KE)(@;Whgb7Lbu)IT9C0QW4StF^hAT2Cs<8yW1M7#*o7%B?Fec@YuP&+g67 zdX0?H4cWu}+r=8#Ecoh_d}=Dmvzg3nCy+dnl-awp32iAhg`Fwv+MYW?i=6tknP zp@y_BS}V@Ue1`Pnaz=%V3!=5PDL(IcD5A1BpHfwUe-XzU=k#>aKh@XNSCn?PHl@Zq zC0CYxAQB4SzA1VCmOn8*HZ<7B>Uk9({iTONZ=!vwuYQ#f(^yxvE)p%w3D}IT6`?RU zBaKv?pYtj;Ju&7z{$+k~EOKIOq@pmFGd#rV?kswnDUnEKrY37ji{GYU zR+fcz<)zGy_Rq}j@`CqaUT#x7?#Mvj_~>xtKc1}(`rT{NAd--hJtX5)pG5BR{wH~9;_;8lJ;Dgf-Va;1 zep{!)UszDDResE`B=+cf4a%N##bH|*or!yS?h&K@kLJYVA+YiJni@uSLmBCT46G|Y zT}qqs4Vlk$zynn6&>SKQpk6jK6Gg9r@B%V7+fG67go}QJ7I~6M#XcBg=XDcQG|+xV zZ07)mW*j=L0lDMk*WA7j(KMLKP_)C|`*UwyH8W@&tBjb-dVs`%*zu%aj!e2+p5iQ- zDw_EUmXAk6HsNOuL&Od%L9V*Con!or16UNr5mOlZ272;4$Rd%k*KXgmhc z;4#Dh za6Q8K=^U08erzV^o=KqC+=KG+d}K4=PkQ7R25W z-;?EfDXDN%y-AxW1PvY2yHc{iY}`=MLsPJ1GxIh_sIBq8UA={2yoy<`D>~m+%4cY?J>=)9@ADF%Jf3-ZiIF! zSSGxViQ_#voPd_XoVI3kZ2SYDKMBJNy)bn;No~;4p^KOqo|}%cHQ4mY(?;ejuro-! z7Cl?)gDG6873?;}@!ed{973G9`>PSOgM@FVWq|dD$#GdU>h3^hSfRR-c%Vpx&S#U7 zu8*HB$NJ<#(T$!st?twwgD@uY4Q^X46jq0J|uz+e4TMEr< zTad%@53B*&ld_M_DV_ANx=jVRRkLU2zl3MVSl@#fg1RVQN5#(uBO0J)0`f%bp6s8* zToGGq_aN}*{>L8!^r#z_w#Wd=<`PcVG%uZVjNUmqAE>EZfw0j5FQEusseJjG{B+*~ z=t@6LQMG~wNl$bHvUQn25q@n{?K3ErhSB?Q8LDSP182|K+K3J=Z?-F4_#fGio&RAY z+f?hlul?-qj_KbW41;fQM98joG@zdh`gj}AiOATqAm{lH4KAd$sWL#{`S+Z7K+E62 zh<;GbwO`y{u6ORU4h?qu7`<=CT6^@jwUOpI*DJj=z#TT}EFkDWwwS34s@&`@b`R-B zJYZ(7ZjRLX5OUwo8$w?-Q}^o{e=Y?^u-!F{Z`hWW_Co;lLi*T9SS1|8`orBn4;r@A z^3nI?c9^~xY%*p4X{h4n$w}St(pJ4wp0}hz@KEJ$oTKXyTm3#br6I4<-q{>be@Hk+ z^RdRX6Kg&SSdW8hZd=$osi$ZSCjhMcqW6mzw@%hq&w-a8H6&I-=!f+TG)fj8U*z+n z*6w~cSznxhKK<=vXYd5BL|N_7_lpN4(ZFV_JZv{L@u@4#WeAX9hoAllv!M4+iNOn} zY?W84=7hd>nB^<*lrFMjhfDIWCB`7U>mYX9w*ZdGe-~xx^%sT-4FR7iv-zHsd$q=3 z(`PeiP@oE%&-R=!KC|!a@A~vy5To7G4(qBqVLQMc2CnZzTt%rYuKu9dVPV9dzfyD* zvc@wiwFdhK6hdxbT=0npgnssz8~#hOa4_^mqD^?kEfCHi{QaGRnL$J6vQEjq)r-gJ zt_o({I2L{2?h%N@6qllmXsT?woqSFUaz4jYZ2j$IO!v53aMdla^tr(`)MX5A>3Twt zAz=vM=A!psEY^ms8Jf2(c;CV>1pA*g-O+x?YNT(3x+B}%HwpD_E~sYSHoRrjb3J2+ tUttpH&agDp&?q6F_)jnC-{NJ7@tg{{zrHNrwOc literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-rss.png b/system/images/media/thumb-rss.png new file mode 100644 index 0000000000000000000000000000000000000000..424295a88d0eb659d3228f61eeb85d5f7500e08c GIT binary patch literal 3091 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-sql.png b/system/images/media/thumb-sql.png new file mode 100644 index 0000000000000000000000000000000000000000..56ca91d06e8b1431b69b49c5883448723d3f95cf GIT binary patch literal 2929 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 IPDjE12WpWHQvd(} literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-svg.png b/system/images/media/thumb-svg.png new file mode 100644 index 0000000000000000000000000000000000000000..f6dd2103a6efc08477debc23df6fa0659362bd9d GIT binary patch literal 3359 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-tex.png b/system/images/media/thumb-tex.png new file mode 100644 index 0000000000000000000000000000000000000000..5903671ee3bef8a5aeaf7480705308e9636e2a4e GIT binary patch literal 1716 zcmbtUi&v8c6h~zsZzPjohO4G#rW{Qq6w^--1_^XJf@_&fH=U(sk7X}b_(Lq*IfEC`lwR}dGn$J-~HnFiDR$l zFL4%MvLw&VRxO2F&RP6Ru{>+H+h+w0bE1cO)1t+y13}vgXdR`elNZwHfIPPvn}JF?t?D{8XdoY4qI1 zCDJ4dy`*Q;gb(bgSF;?;>yL>9PUJC^YzcB>;yZ)S>n?ON=`uX3cMCfEJr6;a4d>_d3$olEyPVOnFJmczP}?HyeV20GVhU{K>2lS{;n z=TfiNML}xJF#eIg!kG+1$nNveu4Ernl`p){SN&;)A#TFJbsT37%2U&4cuz{ZB$7%G5wP;R`chB z+5_n~i`~6TQQ;Tu2?hc|nppG!l9W056ENZ9=RF$Hb~uY9T8;)V?x-7IYu^9vlLAs^ znr}-5teM$-5`1>B4jXlLTh=J#={CfC$dvg*k3Y!Cp67zyP$xFR+iwp@ZBl3g*5_FuYG1KO(MGAJDKP?*IAgo;Dh?Bi z4&P98Kc}rJF$1scy}viD@P>a=swkR7`m zZN|4GJf}^ye!L1#mo(D?BHCJEn!&)otIbhc!|XR;kDOy%XswFJdBLCI`%^J~D7Z0{ zfb+L^YYO*5gI_s2&Z<~Q^4B(gV9q^9VgJy48&)j1u0I1-DwO)BM8GkuxRQm#G0Sq+ zH_()~SF@2HdZciL5%Lw#d(OlAT_wJ#(#{>(LVbn))qh`3yM3U{^?&?}|3V+c7OgE$ VQ_U{S(|cD9F~{N~JC0_c{sk`DkhcH; literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-tga.png b/system/images/media/thumb-tga.png new file mode 100644 index 0000000000000000000000000000000000000000..996106a15b91d4984714d7a6db6cce6bfe9a7180 GIT binary patch literal 2655 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-thm.png b/system/images/media/thumb-thm.png new file mode 100644 index 0000000000000000000000000000000000000000..e7cb9a24b1027dc39d37e0e7f65821887c5818ed GIT binary patch literal 1666 zcmbtT`BPH|5DrQd5fmlf7E>EUY}F1bSVyprkpTq30xFdvDtM4m!2rf1f_+gbD)PWm zjEDybM6PhDQ3$7UDIoU^5eYCHAtYelnfDt126txX+i!Pvf7tyt_K=6OsqtE48jWV^ z>ayR9Ml*oEy3u@nMtZV_)Pvz!;2=Pw6~r$lPSW+ zrGiA}u(BlGXgB=O6UhprC<=Xf6Mb_Rugf3?>d}&TL>P$GiYOe>jdW5v0?EAyKX9i= z9h~At5jfFTLs2A!!4!s|MStstTj59EguDS3c)+Pg(7Y>%@FZ680B?IuPAiD63i$C+ zwBQ=4nb1uQVby7PLoPWphD!fIp8kP1zd#G4@QxA^*1&>;6iLF5d=TL&R2qwjgE3hG z{Nxytb^?CvixytT8ztoI6!EEv!cpCXoFXv1<&{p+L84!fXU7pSo9HUX-aXNc_mkQg za&8*QzJNCuAsNAV+Z$AJiByl{AF@fUS~u2%$r5!_pV8tttUMXbyNsltMV^NsIgvz9 z6@_Za=@I0qziy}*t4bkNqvV_t&F2wAABj(m=<7S=7Zq9rB55ay!4If}OU|jV%7@t7 z$3%ZE(OE`xSL%k_NO%@bJ&fu1*QzqRSe!ql5tKqF-UaCNX;age--~&D9@3u z)Zg3JbwA*9saM(mh0AcJRj7fgjr0R|nFfyQ&Luduq;0+Fm9smNtGBLbslhzKn?ApJ zs?(!MQ4ZyeQ&atx@?=^6sMb-hly9-s$*0@$8e)WFp{`=C-QM=5h+r-+N2~=d?%W5* zOf;>*;unrfBn`5bS#f&X{dE@X1H}<+UQV_aC<~67&TCKI;R?krF3eJA!dn@vaA;yE zL+l`2&f-G54ticIWA^Y(>_Vgwh5Ep!ZBM9O>2g*y6u&1_>IlsPIxqI;j%@>s4KvG> zFZvpjn3iCU>P4RzWGvzYiU-b{i(H_IO03c2@X=ssemFblKzWV*F1I6N#++-qGn09p z%$H}<1BM%JrJGwQO0dRGW3SCFk#Bcf9D(ivw(ZKuY-Tkm>-H|{x&|^9du?UQC-z&q zykW-lN-Z<`zB@)Bo%bO1ZCEryE5tFXJ?h?VIS8TO*)`o22r3$Y1YPQ?!Q^#qlV5{W6wG?eI;0 zORuFmVFwsw%-JO9c6b#LcAofAbAu@W2?sOJpVs(2=^m{9yxYO#Q_TGJp!3U^mNtxlwPeF%M;4q_jp{st5~DFq%{R>7F@P3rSYPM zGD?Q+Vsgz{fsjA`IkS3f)i(AMbCx%hZ*@6a(KR+|`!0N`5hs&5D^Z3Z9Da!BXw+Ui z*qxBh*Z_t8O8e|kV6T*#YCqJN?F4Y>uA!4t_CMQ%JMW!MXA1bNykD~3{;)$VRD%wH z&BEV{3r21POp@`NU0yMVa<`OvXxs*(d+GJ7SfvSJ9uF9CgZ^7ww9N6iKb@D<2v-0Q z0VI}ZEm%@5Og6MOQzYEmJ{K^#O!O->6Cq|=@%4I%kR4{bKpTN2$1ySz;4D~lb&|h+ zmFN%z>(N|~Bc+k{7E3{fapIr}r*bWDn~px)R%ffno)-CFV5phM7n%vW3JE}liN?t< zzOjgIy*kLP&&pSBZfL!l#cN)x$Y(n7WzDUDgXSQ^w6#OCrc-4I++MIK;BaK<>0>F#SgK(Yx2h|t0BCwm>|dsC$?0zUf_;m3`C}m( yH*5ROt}D}L18aL7(&t7L?E?ni^2=X)uUC=uxjU;s6V7>`uiVwiW52}l1o%Id5q{19 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-tiff.png b/system/images/media/thumb-tiff.png new file mode 100644 index 0000000000000000000000000000000000000000..9c80b01742966cb8963fdbb89d50556d010dea70 GIT binary patch literal 690 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9LzwG?TN?!0V$SrM_)$E)e-c@Ne8%D+ zcPEB*=VV?2IlTcsA+9rfs#eaMuxsP8Q%ClE{qpJ8&mZsJzWV?F|F*RYk00D|=f>r~ zfBrmw`f%IYMeCMKU$bb+?#(L>@7eU{_wPm1+P18oH*aFYin;w8m(Tk7iOTlf9~JD{`c?SxfAM_&+Xf~X5seri?3Zey=mp#ClBvlI(uyX zBAd-Wzopr090xT0RR91 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-tmp.png b/system/images/media/thumb-tmp.png new file mode 100644 index 0000000000000000000000000000000000000000..7d4332c32e4ec3d125e9678c832ac56e205a9ac2 GIT binary patch literal 2040 zcmbtV2~$&t8jUT%2qL)D6qk7WM16>g4_B0>7omirVgpJMM5)mT2xvfAlAtfag++-0 zp-~ovL_`E+6J&KmgJ6)I0D=~f#b&#?$xU*z6n{g{%$aY#GvAz@3OYQ zRX%wFKP(al1xl&s4KR!}G$`_Nq4b-Q&@)JLv-;gT-R!JEt(GM+&lFW>k4(DFRqx+mx=Qp4rTo&V-!K_LFQey2^gxpB>zo}5=~AEc zmHii(rq*1z)7lwv-4Qk30WpHv==CDLr*K+neul7ZigO^=yt@c!bLOM;x)i{Z?qaY7 zwN~Wt8JEtoEyUWD<;!>jfW_u@4ULY)+lib_Ih%?0Ag3~a=xfO$PIb8zFB@3Bu3;oN zp3T!xz;~?2;dUeAbv!NLMXdq&ASd(+jZQwqv?(_ut(ds>(m8n7Zg6NX*4CU#>$(M} zk{wWy>~rz3)kKOqG^yn$5fBm(Gfm&OeR$7$ zmy{AfaG+~Y#OyWE*wyO~Zf^jZLaF{(uq?$=m%V_r)W+rRu%FZL;*GfO!CGI#hG@3z zXLzrjzPd=Pf*)Mlh=V@Eac5aT4Yb@y;H4 z=Y_iknaNy2&$r{3FQ=PRZ|a>q$Bf6mx1b`cEkwRMYH=JqnNkf+bhXjC+r8adSk=v! zMAa0RyN77Ae-C>wC$L<6?wWYj^u+3!Nm{x+F+b;FXNBns8>b(0rGeV9i5wW>&9c(& zY$hhMf9UF-@64igryjpD%WA}%v{`nqXbKHG+xaPVru9JI{D8s#5J7faspjcg*3z(n zM+>{3tizF(k>jeL8e~KG8U9_jJ}?+vk0bra6ZZ^lOB{HRJzBa)ux^(Ln8HdbDQq1x z92?3_d>+zg8Y-#Vfg>%qX@0Eu8F|N;$#~v*?N?T#(-sr(S60%Ro>a6RFY&Pt@(p2s zY4zP_#j6FvGdh0zGzu>sxpElGZTzAvakJtfK&}@(kNjKosGXQ$=}=|6^Zc;g%9iFh zfA8In3!RAcpFVS*xH6)r533S<5?-xe5;t@bMU+F-(xkDznbJ?o)3kyj3z*M@>0t&a{ zxquE)`;DDgX0M%Kwvo^iXj1IYP8}64Si+!St-AqHs=%_rkFCW91PqT?07N}Arqui0 z7{gBR!L%*-mZ;R1onXqnBd)N=kvf5p)9VE_%-&ss4O}1!M;ZgTo%;zZO^Tz~R>zM- zw*bmTR3IT|7q&Q-eVD(-e!YM_KiP$k;6ZJ%5*9EkMePM$EYbVi;u|8%aI;s1Xz-t- l+ieX?_I!K7{|-h=Bc`BX(~P)u-nnm1ULFVC%iK;d{{i#aC1(Ht literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-ttf.png b/system/images/media/thumb-ttf.png new file mode 100644 index 0000000000000000000000000000000000000000..d1e35945661b8cabde6a560291157a3515bde2d5 GIT binary patch literal 615 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9LzwG?TN?!0V$SrM_)$E)e-c@Ne8%D+ zcPEB*=VV?2IXMA7A+G-qNBzGs>;IdJ|F2H@|KZO6^9}#+Ec*`xmwW%;UG@LxhyQvR8K9RGhT<^QRo{})>SUu^$>sq6pq z!~gHC{r~&>|62?He|h@<`|JNFbN`<%{(mCVc)sT{pc`g-x;TbZ+%fpL)%3y zj-C#Srto*4{=I*ZX4blF*;R)#b2iWUa$eB@dn zK5=-*IfXOY)BEqvfA7_9?8qYMB7jc(l1;fir@B)=X}-m!npWpmpY`VH3A?+!RG9rt z{ckPfzRvYo5e9pEmoGkZak9eqxO=QVqYM{`ZaCZ~u64WG^VzDFT1&@B2wk@{nG8 QuK;9@r>mdKI;Vst0BzFXKmY&$ literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-txt.png b/system/images/media/thumb-txt.png new file mode 100644 index 0000000000000000000000000000000000000000..fcedca47788930f7e90a8e9122bbab51f8ff5f23 GIT binary patch literal 1853 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 qANPVvN>blvd#wGAxBZve2V+{bN&I`34a6t literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-vb.png b/system/images/media/thumb-vb.png new file mode 100644 index 0000000000000000000000000000000000000000..1d2029b0fe9d47dde249a8ee5a4e783ea73db022 GIT binary patch literal 2074 zcmbtV`BRez6ON;Zf>a%eSj8fq2x2@xt)Q(bicq;k2`YjIf>i|S2?3#4K!F003JMhc z5Cb9sW(E3t+u3Zi0f9hvK4-kn zfj~c$)e7EBC0d;S!bbYG!i zvBkZBu!G2cH^%sbDL^kSE*clKcvhL&Y{HTrn9N2TC8?kM$ZTF1ASVt|Qx{A+zHC^g zq7Bo2H!f(Vf!8!%-GWI!OiMSJ3_8R7069UbqRa~06&hf1(Ttya!G*o1Vu+eHC;lQ*OiwUhutZ&hlqU+!oJKFy8f4Qz z$qcKcpZr*@6LKV2{M_5g*}^eKE{*?bkeV_|&mN&aBXg@cQtTKbhsOIPm>=cIhK8uW z$<<7qK}KZ1AEG_wNcu+TnGz*QD4zgCopKEz*UT~mXt{cJUPWh#x(!C6K31%ux_uVz>;yA1(>tNI9tV$HTyBxif3SVi zArF@kUIjo_RC#dyH+PYOq|aJ;HplSl#H;eoDS#gy(-+p&wjMb zHMHT9E4zR1m#P#@Li5$wn@1l-5d59n175IY%!ory#zVfUm>_3J_+fhRK!0i{(#Ki5 z;)MDed=WSJi1*_vE*N|O1LW6{^nD#|2nTlfhiyZ`!0Bqp*YaJBDyX?*>+@9K^+R0~ z*%{YD?w8md*Tf%f?g@7$XY!o}la@vBy*A*jN28>B!WyP*22=8EE^gOCJt~s?p7=#A z?=6<2Vx7r?c<~=JTlL85rj0R<lQn)!L;vSSqdr>~zo3ex(Zz4aauK3yY`lXqVXOI?#3 zwl*Mkd^z7UF)YrGb=15}#VvCSx$O*)mY2W!HfQgL|Obh6TG1k)i;Vl7-!f(=xC8a6S$b{HC0!}d6UFxx$ zo&S9oU#T>8+;hVl5+TGnHT=5dQ`C;EaHXE}cCO}QtQ9fEj^8eNU$%vE_96C%fu0(R z3=$zV%ysJ9lTja$P7xt30q6))&o(wI>&1JCva_^-{vPfJ>hMQi2kID$Lk>?S*~s31UdwnwUgMI;|fDH zQ)M~+k9gqmtSt99Mc91nJV}beLK|a#dXWDs@^g(>X?3QVvP6-@$u7Kf=27e~2J^W} z3mJ9TbC=+|I~53Vg8s^ljTXlLkf%@BpXlH-suI&LX|SyS1=A-BYkk^6{Pu3|{&7u0 zVZuQ?vK>GM$tqiHvUWL~>GW+b7P9lw!8NCy$4Swl!OcUf11gg-{t$K>M06NiTCMoE zhB0O4cKE*pA1FXy#A)KEAm!C9O(+EV?)rO|RRvu{hZ=00e7PH8`tJHhcR9>{->8rU z&5M+P8(rXKE>7znqfOY1RV^^w^yJcgr5_6VN~ju6&x$t|+{a#ywCn5Z_i7R&-adtm(!vY-0ih5Nv|A z4MGP6V1m}&^tqAAj5-*Cn^33Z-19qfq*5n53qH_1z`1bu*+v*j8~q+J_TsLxkNULm z*8=ON7Ta$bx|uFbRqHK03MXz9+-!2~e6T1Uh?!XOAI|B&qqkYYO(!~(bVXp__KXr|Im@e3Hw1QVP2r2gVWwCyZA2zt znAbf*=Lizo2Y6yhcr(ErfxT1Rvm%ks@`Y0Q8Wb};ClE4tE49OnuLBcKm6#6%T04Et z2Z_61*IPO;KEmSp*Y))Bo!Pnw8Zx1ZgGSt?8Lx7WK`rN%Pzs%I~?4If7U)2(b8YkHx^jm_h$#GpUJ7BOm$5y;F)X! zlTDsqdQjinJjRS_?XMo1eutxWO)mVP&*5j-OCm|#$V|iN3=Bt2`a$|RF`tg6)C@C* z7M5ONN2Zrn#@PJ&(HS~h;91@MkwBjph@Q0cXLJt5p$5X52_56}{j(g_iWt^QecC>l z@{9bsdz7-U^r)e)Y;YpFjaWgR`e$$=wrwD@bEtE2As@gBP0b{7H~l8#AHiptGeXIG*;AflVFXD)&`MZm)R_PgUZV?YoP zD{g(K|GTiLhlAbBMV0L(M-hd!&xlPgxmSQtYd*`$CZ|nuur9u^`ix;r!Z(D3Fu^RX zAE&uLtmV_`Qx&R={2&A^;FJ;SZe^mwz37OrCWObp68NVWhmjmSav@s1U6yaQPs1P4 zJgCB{*F|XEjdBMekrYb#2Ykp4)7;=53a6O)M9&1hR#l@51qGX(n?E&j@z@t26mj!J zZuz_Ykc+Q87f^{0%4rA+O7p1g_I*3J_}0ks!R=@frZW6|ZYad*A1}w^L2Cp<#i)XE zorMRUF5R2X&2NdH2+}o^R5dM!M4-Ouu(we^2?L0KBb9lEkHgQsIu0>!C_Dize|w4_ z6$Q$sd%ud-L$$Ydb+5aTu==uk2~0fj$*Da!d$6F?Z9oI9m~aFr$K6_-x%TxZhSY4l zpvB7g6kF^$T9~M}RzcpeB`?;U)(vt2Zg*Zs6}qQ!L6#$R$|gp78CszF)@tC>DHREQ$K6k z+Wno7jNJ_%XW_oWJ%Dcn@)QdvhoqI59#OgeZ~+wHwXxuh*%Y5|V3>AfPJ86lG+5sp z6vMj~;&QL#&Wf$87j4SI-1Pms_K9AS1yRjj?>ji1%JH2!@$oIZV(u@!K+` z3)qBUCNk)tFE+Gaf#(imZjcJ`U+Rz5WT8y&`r@>^WsS+PbbE|4;0cG8y{MBkaX`g> z$DN$pIBSizFxkgu-?|#(L_P4m{ICqNx`Q_P51Hq8f9M@!7zydROvqiW`Y6h4l(lJ^ zYa*I%IA)ug<)>eKb5c#G0str$!D{)H2G0?sQHjn*9)AYJz~DF6*wH#NAT=MsI_F-V zqh0b0N$8Q-0bWMph{`;akS)MBpbo#T&~7Seunj>t1T_Pf@y~@&S43W#o0|VO@hm5X zd%3(L@vx_%@orQ1)U9q7XtAQeXzH=gyKQGS2+Xls<{qN()`4Mz&@GO}P_^>bo163% zR$p5H*eehXuInC+i~@MH;22}FcT-QJQ|Vc_F{jat8UEAkSO zPwIf|Tdo&^>m4f6&6w=nY77vQ`k+el!g4eencm$xepHa>cYF-ztT#o1^mka)XBhT| zolAe=ko87)&n4#Sa?u{_-k_E9D$19RRUHC=!{+Mm4zKwl(+(Od$Mro`pW0px3lgk+ z$O4vKIJURgWiKJ%xTdiz?nkEu+7GUgDCXtY8@%&giWDn!umJ#Nt!G}6U$1*L%FGk5 z(dLk_=w5d{VD^RPefm4yLK=mG2U%5yXw~*3*OC{rs(<6YJB+6jk`%vY(aNdz=<8-A zq4caydZTSjtQ0uD~7!d*^R;%1$8>501Qx;|LgF9QAc5Mgc0Gz$;thOz0SDge_ zl>Auzs#*V)kwkQ!Mn#JGb=c=J$5XlH<(-Of6OASq{Ig;B2S(?Q^V`rz!wJTRy=J<{q`aQi0YBCnEjV>te|f*?hTU)_nop)WevKW|%9)5#VLKR6fc zu8@WmhSaCur)*{!UU}%6~>KIwBDraqj`0EjW$5dcs5_SYPEVK~7e+yL>7cmUfTIXc6O!9J{87n`V+fl_l$> zJIiy9p@S_3&faDw{RJ%o2H?vju%6lMqj%q4II52=yxVop zuc@QeB@z5FUctFq$`J5#0T?VOSm%(G!ROzRONnXPIV$?I?CU%23CPD$H$-4q;DUKe zy#t|?bFi=6@pwqG^)t_j%|n9RtZ$*ifz+~^F_JITi5>L5BE)r^`^Hbt>GB7lk1-jd zOsOm>+0}4DrOic^F_|Jw#(L?wJV|B4Ge_TgAYvb>xFYqu`28pg zp7k5`B8<(`{4&l{9h2P5_Z8g+-(klknXtlB4KHFI6s$R!-O|e%iXAy5jz$lZadW|P zVoEM*-`Uw~2ClMjksCx;$QuU)Nz zC`iygd*OTIFx@@2;OUCB(NoXB=P$oGjedsz+VIhGhyCapgdSfS4Mg?9DX8By<{yU1 ypZbWtb+BjTSzs>sOliUI8~(fh;{Tn)wPhNW&)=DP?RVz=tJv8%T31>5{PjOtu~(P? literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-vcf.png b/system/images/media/thumb-vcf.png new file mode 100644 index 0000000000000000000000000000000000000000..450d349e3d7d4016fffa6123c54842656ef87d74 GIT binary patch literal 2581 zcmbuA_g52!7RMtkieS_}ls>UotZ20sc@+^6L_m-Lf-DI$KqJTyME0g=TSQ)&6-7Z5 z5+IB)3@eO)jIa_2Az_3NG9ZK%vIBvnwEsfyIrrRiKj+@>{pEYk&2)6I2C8bR0ssJ@ z4am|N0638QcdC4`Pg?y4<@@E!N9OkC06=4o+OEIyet#g!+1dht#DBv90F)j$-gC9O z(O>+G+#%cB+mp*J2R}^nR!SCz6$(YtRGY=%hXuj9Tp@QKt&%Gg0eGZQf3fRGrE!0W z(=a?|?(@1-_?C)|nQS&0C?RdJKhj3KR_4UJJ0t6Kp-c+GmbEOCJx38cd%N~SFv;%D zqHx0(S95FN{lq3~lDkw!8>R49%NNI{wwCAktMEnK43A?sRBkp<8Z=%{6|6rYG%ni9t=iv(Q zXcd1)ie00b4!oDi_n`PX8*Ewk%G{rn{vs-t$X(8u?phY}JVp^NBNc8Vm7OcI0-5x; z=`PXkcJO#ThC{K!e$1Zj9a>-X9;-$zksgdxzF!z#k??2v9J+8bme}%>=2+4@#Fdr;kl?v*aleJ8OP__*jtGPE+?lV?>XYj*3f31l% zRY}Lwgd5v?yS0po*YktYT^UudwkzMOXH9Nyi+VT|+y>*tOm`fyC7#%tJk{2}N^M>u zg^V|hZ?a~2oOE(W3aS17vpt3L1H}tN(UYi10xE2xsf|6oCKa$mTUoO`X(Y7mP`OC9 zji8S+M4JsP;vciU&&i!plPLa8E|O6U7x6Wmk2^J?3x^Ke01tYL2p*=3SVttnWx;A*Z1 zy(&fnT(4TghSxxfjMG41TgeKkyosCQ1THX*f*gztm7b;C_H{Kmr9|FGlIo)tkG2`s!8tc%rfBkg(tKIzQgmPB{1Z zzR^-|G6zoe-{=EEcVpG$`xWJleLHeE#8#e#qN9oMZ-i8Th`^P9ZXnXso_-f{7Hq2M*=MRB777( zWWU&ys;A}w7S>MM(1V5X+nqp`9UWxy8Zz6akHKoT!2KJ(hG=1W^JjrpU=8I-rw%{e z%-0fB4@4j6L0oBV26IrYx==62 z>rjoM%BFblUhiO+nOM7CaA>WWCkq*I4l^0!iDva^;uX_S$vo)O*dF7(Km2!+9tWhyfFfnEy*h-AmRdEj@yynk0*wtH7x8 z4vT*#V!=CFlSr;ayqa5ysq{?z4E-qZ! z_7t`Ln0r834AEZm5U8&?8-D_yFXmwz_F}TKbgxzLj^+-~x%a6BOw$6})ZMU%=%6l~ z(xv0ndnD6ldR#s{AS*v~q@^(k6xa^YK1{&5Dr!ll7dw6p4~bS5HRapa;&rFhbk3rS zR{Q3vwCI;x4aAuh9)_y-2Nl?>p_hanfdqDP7{ZJ82tM$iCrL-z3~jOp&AR?mJ(V zAFoejgS>BiC)wmN-(??$x%QrW7}d&enRJ=_ak@OjG(k`A*UImEX`j`y@h_Pm+W z%lOvXxE5)xd!_Jn5e}NzGG6n?F)@WgbZll*rD{zNw3is~5ct_bZnPo*NHT_~R03#g zk1r;`F662nWho^fJ`jti)EhN6jc$mdL%B`%nU#tScb(iHHiHv$Vpfw3jESKmG>Dg^ zL-;(oy#0;Y)j$W(u?^~PT{~n$Su|X?O9>GOO$6ATQA>-ojY{z5e|l?H9c*`WkvGYi z&BC#G@`<-*KX?`=yGu{Q_gb#=+$W#fg(xA`kyFRr+IZ#cc#;WOC-vG_Ao51$S#JoO z+23_ChkeT+{o2{?qSZg`N$4|(28t%Xk`;AdIsbKhh5Y49pHk}eI-Q&pnyusn2$I%! zQQnlu?*76`@p1Isw@cyf$3eY~er6`wn4k(73tCPrijG5$N&W*$R}ag>*?0Gr$xkze z1#?u|=H=DN(_u~EBX!MdizAin1>smHr-X?$RZQd!-~IAm{IGvkgWzdj;J&p&X8Y=s P{{}Wz4wgs@zh{2|f}?oi literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-wav.png b/system/images/media/thumb-wav.png new file mode 100644 index 0000000000000000000000000000000000000000..d56a77d3835e96cd211d4e19260cbeefec74d183 GIT binary patch literal 3601 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 X4i)G4?TL2`|D&F$JX0=Hd>;CL6qK4O literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-webm.png b/system/images/media/thumb-webm.png new file mode 100644 index 0000000000000000000000000000000000000000..cfb6bdcd4bbff86f13c309e2a98d4449a7ef1803 GIT binary patch literal 3843 zcmc(icTm(xo5zQ&f-)FDk`B15OITD8abN`oaY2DW5JXT!l86W_umnMgib&8wkeq}e zCx;9}Sd}nCl$?WrAX(!4;>?S;x7Ph}_10D0Rb5wgcXdCXdQw$aefz~VqSpR>BKt5H z%>FCdnuZt*8|nMp!?Al)Y1z!&-8k(CdISuH@`@L+*$)^grr^8B0NECu z?nXwN;r2ox!-}~&3*@^%bx}apeIVN&?k;B}-#{0}q1x9#h9%rjg<3MB&`)YukhhS*{Sn(XJ3We(5fTe*j zEsMFe3KX~@Qyoy#TZl>mi@X4`4fG)ita%Bf-3H!T!1QdO&;x3S0rLJpSEgCu4)h@z zEPe!5h5#AX$V4kt9}P8UFgF&NE3?SwPt45~xT6Se%|kwSffQf3H5Z*51SwB|T<4x& z58lOK_>`|`5{$e?R>m2AM~p>U*7v5JqNih5pBZU?{B-D`w}SRs8czFGXvmhFaVXd2 z+YkK%_ZK+a@;N;UuO2TaVdcBx?XKP_w(Uf|fy|hAyG7X5Zmu0K)J4*5nmLToz_2~V zuhx*Rd{$Bk4bb6#3TyW z=|D+4b7`tc|w;B>h6>giWWcb}1)ELy;|y#6TtiO@#XJ+Y;&;ro z?#}-5#Ql0@CdPH}E`@$O->lqhb;zmwevYhi^g(ScZz8pl7sq8Mso&PpsjeF|b}#bA zW^K83P@iJ|iv;B;d0P3HLG;l^pI;Zr;TE3{*p!XU2^P+!ms@w;zuaQmVZ?(=sq`Wm zl2p=(tv9>GwF}4Q3=8i?lv}%-b>A@ER5^v4NF6xIx$dEv6ipQmjE)U;+&OLXpkmag z!a~Yh`%buE72A467jcigGLqC6t;~70-!)^BQ^t(lXQ!|vMHsrGC)()q*ifEi@=>48 zo1#Nnb-4{5~oiuRJH74 z{XSgI;BXL@nq!N+=g?!o*ALhZBGnu;o(vvrcH7K+|Ff4;wMu&s5rbcoDn zHJf2rOvX7&-${z#r$gq@xeedeaN^4YAHQw)Sh}o*?CQSW#oqP3~Z^P?iII8CxoyV8{a!e3FX; z&uKm`U@vRq9rM2l-(K;XU-e^OTWAOg-!LxR*RUs(edDRD&xz?NVrW~(AMkdU6X9Ew z42i$d<2gLXx7h_4`22jdc4aJ9S=+KN@xb05^NK?LFmGCcLXmn(R14}cpLD&Re!WqA z@KXJkHG#0fto?@d=koZ2xg=GsCS(TkxLcd$OmO$AhQ4v5FRMlXexxh;vRh+M<`?@Q z*{DjQaU{+*UB5T_o&ju3tf1jJ;*a&@lj*E>ub$;iB}zNhRMA2frCbprBLRUl;#I+r zM>+^vl=EchLBAXK#v>ZV^A+ehx}%XW0?5H4^; zR!Sv%USq~|^~hg0UVwdiJ2G_|d!A>gOZzn2Tsf@Wr7EB8^V=Q!@s|t*7hK8qyRv4J z8U^QMKReuwL-Rr-=3t70A8VAs2!EL_P#u!eVE%+)e30wlWVdJa3m*K>Cl5ncd4|{M zV*$FxqnS;)^%LNRv)N#NYKAwNyl-PsBMLLyVD(|VzUKj4n5}|+WUiW({K|}Kn?F;k zD3=~7qenUto2YjXyIJ>DWC%}7qqET1(Y3=iDZof>6(y<-Rq;jdJ4nBo5Es21+w`bY z-S=jhnlP=QF`5EpmlW2$7Y}i_oET8;o)mR)8I2B}s?HZUA#zONKtsuf&#l+~Nm8FC zPZRmaWR*Eh$zv*=qS&vzOU_7#Vuc=Lr>WyGBII6tcinRxSJKrJ`m*Aah~HWO68n-c zdoAzOXja7jilG-V(B4*+iESrB(UWq)=Ag&vJN#a!hf3QeLd32rl2CPyfEYt-_^ zAve)f&937^i%NLjx=F+m-3 z7fvgFiJK-zIOL`Nkm=E1Qkprec_33+;<;23)%0X=De|x=By)$#ZhALDT11pXa?NEk zBRrSUpjFVyzS~e^Ne}!am?KRFqi7L4ls~M{r09jqeee)p@-=jETIId&B;;c2UdZ`Z+%uZ$;(>Hj1?9 zw7RCk)7Zr2weXr6Sy7zpFa53W6zV3_F4YdTR@QJtCZ9r2?AK8DpradovAs^;oABOa zT(t1c5ldUq2o;h%o~Sw~#v@rYeMh@Rz6F=~y4>y|pG@Lc>}*c?=@x6k%ctVO#U}pN zq)%D3dh~>B4?HI&Sb#4ll*@jvO1Nq~8$N-5xQSAdDM`*N84_irmrXw2F~dI@4gYZx+9TaQdk^#e2w{ zUmOVfNpE4y1}8dgCYltq`v8z9m_|>J<=)y`BxgN&QuCDOT2pv{uE~M5Qg!1fR{{KM zagyRmi4c@GZKXI*o@?WgO)s(U&}?SAf0Fo{qc-_<1^vbC7isZR>lZVlBY2IBiLLvz z(b1Da~$Bw6HE;EkJdd;CoE6CJ2n=FC*=m5=<~>s`ossC{jQ#; z6<6e6iyaq>Wg~$*0={tt!mb`C)kDLkk9qt`nv;rjJSdEbw^OUD&xzSy*GQy}2lQFU zDw%VhogQ~&tq*fhf(?h&3VS2nZuI{-j~lfZc|*q2xZ{t2pWE0&(2OKVpIkXNQ;g&W z7`1z4!9{a6D{kkthb=O^J8alyOEn@H8m+;77X|fys z51QDYFAvW@QQ%;`AL67ed!53~vP8eD)*q7ZeiysqB$dCR@6;Yb(mlB!%p2S%wrQONeZ#+)0IqAxk8(+>jZ|plo#& z!(`1m#te!vcEi}$=k=cVZ+O4wJm)#j_lM6<-|sn3w7IGN5uW2bY;0^t3=Q-w+1U1B z|2qdb_MFcyO}l%G6Mfm_G8h|aLJ z21~zzngZ5Do&L9+++Cjli3W`Dv+36Q*DbsC!Q!dx#j-2o;An$ge`2ef}+ ztO}-Dp#33qy@2^X==>YHqM-c|v;_k-7uv&MYX!(}V3P%t%`nprOQW#DfFJi@VGrUC zL>E|~!NMT41wmIN^v1(LI*e7r;t*^u!%Qd8J^(Er2H(SQ5s*`%?==i$!1fvpyam<> z%yz*r4q5|XZ4Q2w!pbCcJcgkHV3MIN6h^C{H3-J*p~Vm8$uRmEXa&%p3@a0`J_pmS zFiC`#`_PvN(=9NV13j_Oi-qxjVS@!7Phg!1?V&))gs~cE4TPT&us94qpTWR8=)?fM z4jA8IYZd0Yft&`zco-@Kk{7H_L0bs?A^@Wv=KF!1t|zHZVq-gGXQ+4CI%IHeXf5n0 z@l^Ao40ri&zeMb_^k?R|NUSXXLs9H-@~|GKaMe@wtHQ#1r8xuCN@r#t6~4;nm5!t_ z+d8y#XlItRnPlG#wJ;jREGU0=HeGrznEyxpS_pdOhf>zi6;8*CrYna}yOZ-|6%;o=#|j(<14yebhCHN&E;(Cvby$Ks~eT~x_5WeD^MxK znS6aQgXUWnF@qbPh1cxJZl2kv2|o?Z5K~!o7gro)FgTS~<>)10JA~I_wJjsXqVM;# z@+%RyVwtZ@hH9zS< zSZ+zqVb{;NMr(YVK<)=}g)V;csv)2BPJg-4L=O*pnv3s(K@ujZqlNLQAW6*2Ht?C{ zq<}?E>30dz9^_sj0^5B(ETZ9vg)A!vM_dbv_Qid4GJi5*VKJK4C}A*UJHd-DE3cQ$4J z)fbCWd0*@HBpg*Zb$sT=hnGxI=}=4_^ZeF2zk6y5Emp3v z1r6d&pXazM(@oW`w>w%pd3OzSLXUJ|Gt<*+9p3Os2;kzHou1J}oLp zN?X|VGDYOSqvk0SkCC*QexAklUGcFPmcosTbl$u_^(m>N-+0)yMtSMPV;ml@PqtgI zs3^~?=SjL@@4nm)MhqV7U2C2{>^g(EUyVC@CE=BdEGwg6oo}>uzrK{bctXHvZI_`r zqOYY`+x9ZTU)8^^E_=Z#7Nd|nM&&hID93@Y;P??^so( zAR|S`8N61*Uaj24EQ?Y~O02oLBrisbwx1d)IA)e$*b#R0HmA~j46ZY2vwtWn8M92y z843>0@GyGH?y=r|!e>`HQgTjUKS9shC1vI~VWiUZ@;RcKHimR)ySq`1QTX_r9QF*( zu%g}nINIqMhSch}N_hK4?P2K~lt;N{`40E(C96y%oolzimxq2+P?%J-dSKFJv8zGF zshm!05dNaCeRji);VhvoSE1YX!o+8R^Y#gyT;fh7Ee4DHK&Hl=QTpMq&q3-iU8wnE z;>Qar&)=eQEJI59O^SWrB(1Y=l$=nnicDP*k6^DZ$T)J6@Y-MLJ5ouL;qOI|h&O)H zw!4JQ%?fA`Cq=uXolR19a=iouNt_mp$M?vL~t4Xtzj1G~4@&+yT7 zzF>}>8N1ODLDiAtuYNgoYb`x3-YwAQ+M6iHpnYc*j4S!C8{v%dvM34eCG?OMEOj2rAFsya-4we4y zm4e$wuWC=1ZDrMY=oojG(mpe2ym`_L{rTmTI+vBY0FE@--PrD#VxxukV~bpbD``EN znRw=ve!6e=45KrCxLW%n|NY@W)IsK3&%Z0^T0yo6Jlc9r(wPR6 zC8ML}$r4f8aaU>`ZmR@TYaTr4`hMaGS(znF5p|Y4H2d6!vT`{l={9Qa`DBxiAv=3Q zR;Aor0NNbkpBS33K93e7#jUg3p@&{Nq0CR=Th2*Vu6>xxWQ}n4vTLOcq8qknaTM}D zSfr`(D&A!1*Q9{{ozHz1+If{n^4r2HS@&NIoyL%Ae?>C+sGIVKcmvp{vNrou>)t-3 ze)?*5>3ZMY2Pea>$r|3TR>^@sYboC?%@HM(cqN?8wHPgo_oA_?!&=BDF}OXkD1+HT zHSP0s2w;9#KM4nnkLblo%;RM*sob<%9_MQeY3q5|y;-pS(9h}t#{0ZGvR}0KH}PM> zE7CT-c)&CKCc?i>rRXlr>f!ml*o72+b$kDNN*zt!ys#nH=S;uDBe5nzl}hbc?F>C` z&FA_ul$-B2km z&8V!k{e;7R!_+A->)b(vr&X7oLUZ&%1m}pqB{ z*FPoK=cpjX-&GVuGxF!Q^u$SHuXdrzGXvC5ho^ov-9JSoHends4445VuPG_H! zkYk#QC){`2f1B1bb4pTnFJsD{WNuUL>eVtnhn$lhGep^@qVT$+p1&}$H=fYV4G^s2 z`)h(?@5-K)g$B4{PW+6k*xA*@3!KF%pU1vkjdv{OIjE;JYw4^xkh|mOspE1~2tRY@ zMvhFdx`beqs*M!-{0WSDY~cBeDZ~yJ^odIryel?EN&QiW<#qVCH2zr9We+{8I3}h$ z=9}-QK|m@E=+~%tbB5MzBj?kFEYae+>?*_iDARbX7A;M{4iPXbwUcex<`X|-nj{gW z;pTNXWEp8oE;KW#rlml1av7@p+Lmmp_R^{1^Q)m8DosL+KQHq&P9Eev8&FCWvqK2s zuizD9@PrSiZ`cTucs2+l*?TpNEv|2?JFdZ45!?D?yJQe=6Jxg)dCgWZYO(&+1|dj; zzdC4`r^}r-?%@+7&%Ys;TrPCyoFv-lD3>JUON>Nv`d=&vm`Ka96QZ28yf+J>mAz<> zTO&OZ20e(8*uO;#x$Nc-@Wx#*gD{iJP76ghGBz(INALbUdLPtl({`{G`0Uh06 zh>N%k_>)1~be{cL7#vjfRO#qy;#g@mm;UVk@-Wa+qeBi{U#Fv^N4?ZF((tjj$V*A^ zb+9@=KRY`+-TJ)|8|-s_e*X0fydXX4_~?jwe0+Ft;QPk%V1NJU@Q_A3p;D>-Z>>Lm zcpKyd`Ew?~(PpT(+yC9`)6>(bv5{bB`)}11NwCn${I4e`Co~!@@V(vS=&+xI_2$NU zSx#n2c3Lm41OCx3#N`cX^LKt~Vsg}{`pVMLfj-I}8RqBSgKe7_8H(_6otYf(YHJPi zbPjxP`_a{5u%|0ECOqwPB!x_#othv_jDPZYpNWXBD$3hf`}H*uzVvfpb$MxSdg}N3 zTDZ4MZFzA`NkOoSeOnWnyt}hQB9-T65f|o~YpeUZu-S>9mluC_V45O*-I{8vFb%bh zHOO!;mxWmZ)a~8qV1H#nPH|Rhbb#j`dH3Xmy7Y7YbBOOeVS4@7N=-?APEy?3>PlNv z1F|r8e0cCjV|_wca8YIovM>kN+T7dOQTjE#6hN75UlCb=B*?R(E%{8<1tinJEi1gyFuPoo!NGd2tg8xwf*Ln;eh$6j+g$jr-n` z6(5@%9Y)^SDa=UTr%)(+dp~Dq5+gpMk>w-(y@=4jl_g?FOXJ?|?(*Wo_iy!4{vL6m z0d*B6v(uBPvck_Helt@O^E1aB zN(;?60&VH&SR0?Ks~Y(dwq_4QuHpE*c5gCEUjFNEwOi^ykXnlHRH~i59->U1b$eg+83`J$DlqKT%eaZ4L-l46yoo=(;(OHko;vFg+*U z_0J!({15R#VC>b@JL&qT-25)mWBSH&tFyyyl~SaAg$<4wht=%IU`08w-W+6|O5=n$ z0sGKA8zaWtR5+$=F7G9{*h9;lk!N8~!7+bJ_V%vfSJG38523mY1i$2 z?u>n|PDOb1z}0Z%gKW@%z?Spa-E7dkM6H>m6Nx6XaX0-lCYdtRx+CieHGO;)*wAK+ z2FaCb1%(#2%HLp7hR#-UENqsqJDN2F`7yc(D|E}NK<@5&Cd7PzN_sRaAeaL6s6TjN zd_c~sLn9;qPm(8&JfsZ~`wWv;(Ui?AQ|E`Q7)Y@ZiM_x%y!d&FFTm^$r?u4va_tIO zBSw-z=4HcipicY*m0)3C{uU-C)9wpk-k6o*pPv?ZhEaTSZ#SetmttDfr1@`CVIh8S zzkYpclst4d#)9kWGijfgdu%PxRNm!X{W-Z)kuw!@!6xknf1^6Avl4q(p59ha1GhzqSUcpzQ##N%ZQ!(Q1Zuer0_mG>@db&VWpI_#SMX4as z(yQi{a?a29!ZK9^6Z-}E#D|3a-Lm1vwU*pdyaM<=rsar%SVnS*q6Y?3^d1~He)}%;;rtrEmqd+4)*a8=LAZ6H^dnHPgKK}1 zsuOT!>xRDPNFi#hIgM{;PC_8KQZcB<+pBf=`%auL7rrQJV#0!32=++j0PaoLcYi77 zE3Wi($Nl4C+{R{NL%E@G=~=8|v*7(KC_~jWL8i$$6htVIRk})BbSUnUc~kAw$RBfr z+$ZZMgpCYceO5hzv73885lyM29l{N?%I z&$`)k%nxoeV`qh?y56brl$l#+uw9Zdl0OY{L&cqvTSUrDNiFP+0OotK@`Wvyc_4Yb z;bY0F)2eRa8xpeIu+nSJLYsbpj)KT*mgF@!ak-sduviP{`w0wReJdZ_igeX`b7s7p61(|5{68A{WqdB7dJ0*F=Jys-3O zJ_AVLV8nxR3-X{*V}WDCY1Gj&c87fhwAW?LEfmtpi?Iec20j!D~*%p+Cv>3vK;=lH(6rEN+zVxq=}Fe4!2sD zW0L0Z8F<2`<(1u;0Ey9GA?a^f&D-Y(jHoWzglU5(uY3Xj$!?R)f?jt9NZP@pq<4yo zmQ2FnBm>Jmc&Azjm(W?-E~CNC)eaXHUW}dvmL&w}@7xo$(|t;wJx%LO5ODE_#W zu$oXwp@gd-Fpg_E;Js2CH^5!K`-Xwx=KOFgB(Jkmb?i+JUZJelw)77MVIn>XNtyWy z97lj*!@AroZr4lMU9GXEHs;3T(kL-i$z<1W3|_>1R$IVvpIdLEXy7-mKp>Mv#;pz3 zoUVjHL)GfT&6>lGNDd9g5UnXiSV$+M0afD&|FJW4uiY$8I+{~m%4%!&--Y^nIPDXf z+yn}_kvtTS&J)>ZSBU5A5Nc_|GUiQyI32}~yde*zM7c0&jzxCZ@A~q9_+D4!lK`h@ zo-Gu}@+-Lb?h6Jp`uM@A8Ge>0*Pa}iCVjYkv5nd#%ZkPl5<9Kg$_7C=W?u-={C-@1DM?NwM3IBQUnLaT9pKkkWnVglz@ z$-adEC$kIM9(n&UBIQjPDS0vtIE5GoX=?JcN8Iu2RNy(OwR%ALy3NIV|P zZ850yNP@PPk)D#A)U;WNDDDofVzQ2rtO^;n^y%$coVsW)Em9?``az}^iZ2YdUvaN4 z@!PlX?H_Wh?`$bjGy^`xy>zzDZ-_n6;m5D8aUlF~mqzT?ixb^!Gulo1aqZ2rgp{7v z^OEO0ut)rO^pEJBgn1L)kqgoeFd}=mrnhP+#%0GCCD?kisBOf$d1LG1egPDpTRY*N zXa9nD@r1_sG;9+alQ7!AtJ0?;C;etbxIP0sobwDMI)RrHS+rRpTHoPNcP-+7Lqd3L z*^l#asJj$Wsj1GP>I>>k(LPDu8qWa_MAS_Tube5FyCg8>$s1yFe8diV+yx!AaJu}=eiD*NHJO#rbq_-HU+Y@M>RsI3zf3Ba23(inRIR5I)EDIN> zAI>PH9*E@#>bMrVOT85!u|YJ*pgsUnJAJZ5l(|6fXX4gH4JCEIhTT;unTTZ|&PvqF zfPwz9NuKtdfV9mctK8eB4x8@WGham!_u!R}a!-FhzqmKdzE)Nc|FVmBMk)rgC}x}@ z%5}OF`q@f;oqqZ!b3u7`6M-Q>Rrot$=wFD+12E9gE+9=Ey4G)G;|h6#*guf=o(hP+ zakqGhUYr{4Rc_(c1ERb@q*g}q)s9hdJ(RNohe{kthZhzE2=rwKqd-kcfJfQK%9|?r zH@zPPAlt^i{85R7S)$?}D@e$EN`0s0WbKr6Pi~OvW0WSt^%xM~{VYeu>(5ufH}_5b zLtFgVz+)UYBni!(@l_EKm!G7Z{`7(X3-e^R zq~u8a%CPOES?f8v6cBY58$BzLdY|j@rI9JS&*EXAF=L6D2+l5nWv;|02)m|)R?!sD zrivXkP<7d6J~mi(hOFa49Q11zhZPPPNGNQC>}< z>wbZx+@#r@>VNmhhUbwPgFyv@U|5`gNGdg3zQ^fea{S){4>)J?w!iWD2Oo;#qSJGY L7wSm0*P;IhE_R3) literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-woff.png b/system/images/media/thumb-woff.png new file mode 100644 index 0000000000000000000000000000000000000000..777d642c9fdf184343d33cdc7a795e754081e35b GIT binary patch literal 3421 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-woff2.png b/system/images/media/thumb-woff2.png new file mode 100644 index 0000000000000000000000000000000000000000..420fffd858ba5338e32e1ddc36bbd85f11082644 GIT binary patch literal 3927 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 QxBrv|PfT?ywVmJp4_}@tCIA2c literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-wpd.png b/system/images/media/thumb-wpd.png new file mode 100644 index 0000000000000000000000000000000000000000..0c4d4a5e2b9c7e4acb79c9a9531f26ebe07514ec GIT binary patch literal 3127 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 vIvDY+mdr-J^Ge^dDRKY+T5mU8J`s~7(Th~gR) literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-wsf.png b/system/images/media/thumb-wsf.png new file mode 100644 index 0000000000000000000000000000000000000000..82ca9b2b42312deb2c1340cad47e58e5b7662314 GIT binary patch literal 3028 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-xlsx.png b/system/images/media/thumb-xlsx.png new file mode 100644 index 0000000000000000000000000000000000000000..5dc6163c7a7b18ecd737d4a0e91e742e19d473a2 GIT binary patch literal 3481 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 literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-xml.png b/system/images/media/thumb-xml.png new file mode 100644 index 0000000000000000000000000000000000000000..1faa621d301aca1540599e8690e75520529d01e8 GIT binary patch literal 2557 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 M^iB0jAHELzA9Kos8UO$Q literal 0 HcmV?d00001 diff --git a/system/images/media/thumb-zip.png b/system/images/media/thumb-zip.png new file mode 100644 index 0000000000000000000000000000000000000000..8e6abd48bd730bab1358808821d64e2f8b10857e GIT binary patch literal 1628 zcmbtTXH=616b&^f5ET(UfFh=jDvsKMqE%3XC>wE$6-7(csE9QvNCY)biVBV@Mv$sl zR7k>JVJjJAk&sCSE0BDUMG`VdNQ(W{|GsnHxcA)me%+T98xuOqWq}I_1ez5d7P1!v za>)IfPSd8C+7krZR0E%eMZ-X#@*IfugyR%;K`-K}- zO4x%X+HCfV4U7rvoq92g*q4&8*9f%<&0}%3wBIC->t>R+}vvL$FTT z{$cg)#t@H`cV=~PrDNky$bxFYklJ9*A@Zq1$`VfhLn41@Z2V9eIk|z6RL`JF75!>G zvW0b`ijvXNZM0Z3Tf18%@(N2@n9J_v6@=W z8;rpa|LW{HTG4s?GefrW0jP) zUVt{FWXe=0tEozZDW4{4mdL;Kk31mo2Q}kYnwaFlQMJ*WM-|@x!bMX>?R~(2Mqk|B zcf6AFwo5W*n#d;bUem=@0_m5&k<=#UuN9qtGbA-VLtQdeuS%Cg;_J*7yl6O&B1GW3 zo>PRUYG^p_;Q2awC13iU)%%JjsuvFTsr4z1U3ih~&kkODFOWoX-}9QL3>1eMI!pCwvFlBD z;qJx@^`>1-=W2qsAl}T3c9*UzsBIJ!hqya>Au^3#Ytg=8^^GR4pV6yG{0teHxgXXU zpOn7-K(02L=gj(wLLK3sphHGSI2NjtxqPKcz#j>Mv#1ohnK;40xu(RwsvWma`f9oC z?E3E)^?0o1fPI&{HV=_7WJ#s>nP`=$BJmW3e&;0Wf!DRAc*`mO!YGPa@2uUo^2abw zACh7#r)vc>HKS)x7Mqw`rb%g3IPb%q2Lx5br;*vS=3*bcDDX3MW=0~A^-eAhl9&V7 zKagx`;)rgB`Mq*C4vt^NUP)?5i!T{l5g_wUSOnSN!s0Dl9G9ARojaEAKY!cpBaY?@ z7I~d50F7`-j8Da&8ihXVEFm{aiQPWJyE`7Dq5x_j8|L7b8HM!zkk-a-foPe{5W&48 z{-3rh64Gx}C;l|gyU1cH*s`(F2u}~F2n@Y>nDHoam-49z-EXu|te{ z5cL?Uz3dy*IuXcTOM2@yH^lHd>Rt-7GPT1|AfH?skbH$sv;{YzMEL6;!R2t^nF@WV z7&_y*rS&v-A|-0Kde&tpzf1CdS3GUoxuoJ{Ny zHk|C#G2Pcow_Mn~DZ}@r4;cQ;Kc^_@H3YSu8u?41=W%G*XGJ(N|JoWS*2>hX6+u@5 z=S*q1%foYQy|b7nZw>IR1AVcn+a literal 0 HcmV?d00001 diff --git a/system/images/media/thumb.png b/system/images/media/thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..1efa0e732d9cce6dc3873d24fb1ee2bbdf134ab8 GIT binary patch literal 1200 zcmbtSc{JMx9FNkckFwS3ZLd_-*j9A)YMt`1#ZhNnQ&epeLycP1S?5;vveLKdYR=~v zD;gD}qmG0UiG+xsAR(v7;mS?YM9!se|Lp&M-}m!A$9vzuKHuu7h#(8IGiD$V$RZ>- zAO-|7di(|_j19>sWKL%wliT3yU=V1i)?5QQZtzBVF+qNyfhF5LgN8u-JYk*#_@84pkFV1<*Oei%Pon9YtIkGtO83yzDB20X|=C*cys%^4@tyd*H>};y@|_&_TufYSp{*Az<1-1^Ar5PNo0E#i$keL8n6xI^`WTOWYkszLh0wc6f5BnF zSd4XvC=NHH&}e*6h!FJD9~9C&f3KcGDqO}Ns#K#q_A?ee4L?^+CU$Sq@_>c%6@utU zSwmjQ1s2p=E%Q*iuXqgz`Plv41RP4O(@yPhLoriz6w-HiO!gA4i%HF2UigzinXrc% zT04dd2>{35rwZvo@oP~aD8$4u-v}IS0=^S_JbF;UEEP{GnEG)W;NA~iC=|EL7swTf zv=Nrg^C#2N4DG(fNWMknW=WGbk&R(Agz2Tiiy2mqec5f<@(|bq_}AFfYF=3BfqOP# z1s(N|(@}k~Irjp+S;zHA#9KSBwI9ukue6_reVO%*Qypb0sfXPk{!#p>e{WaW^`4ZX zsu;`|kbAqSn3{PKM(ic1314vLBYrFSHL zmg8T^6Xw2K=3S2b)b?rOVC%&a8xx=`nPg-6B5tS^Zdp>BuFE+0j_LUGgr&YpX5{4^ z(w2SWY5dLmHY09%6%Mwkbt#@SYgt8`XU7TCo1Fjvz+dB#`1DcLK5s_gmn#C zeqMfUGU&;(-+Uu2aue#Byqv!(m1SD3R!ywg8SeyC_ZH@rtE92L&QjY9g7-B1>L+2J z@i-K5T3e%2%kS?u`$YKEIkiB$n|&JSjZW<%K@r@e&kD#%ukGabp$~nvf$#~NF7)u{ zBiM=B7PRbw9Gdg;TqLG|Y_}RJbS({csYTU>2~N>Y0n5FO4J_01B?0U4Ucry-h$Hou z?2b&d$7udz&yo*Js=~8;+(VH>V-PEHBmV4FcyQ{KtyNfP;K@qx|JUsy*bTRt5q*O- g^xvs|?5=4{Sbu@U9`+^(35*CB@Jp`z7mNsTV*mgE literal 0 HcmV?d00001 diff --git a/system/images/watermark.png b/system/images/watermark.png new file mode 100644 index 0000000000000000000000000000000000000000..0f38a75c40d48ba2fd062a82eb50090f4cc61133 GIT binary patch literal 95789 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%0D2)uoNCMzd4 z4#kTL#oXu@n|bCUT5!dX{{bK=muucT!s8qNM%;`z^jbhDM!bJ^86LrH?o%%R6+eKa zT*VKXBBH&y|NN8X#hm3vF-YYZN*%axg_A?&jbNBY9whVQs|4n*a1rf>oYobl?Zc`a zvTE=D;!u?;M17i~Y)R|7JnVD#H5bvY-&l*J^!X;-2eDzot+=>Vn5RHiuoooI@7`Za z?MpvT{5WHV6v@!FyhS(s{;KfOp{nEDY5^OshFiUj1mNX+gA4{Rd&=`2T4PkkPGxS+ zMbU6}1PonH1t#*^?JQ|9hxN96P^JZq=r4-XIil`PVr5mJWTF4gUKVHe>e@+13Y<`4 zNAQq>Q^62XL4EUYw%F$YTZLpN{YkxzF`|pV8m2gA45qW=H07gat$xR0lxLNQ`4;YG z*LxUdO=$~w7zuO|=V2!1hk4TT`7CSf^_nsrRk58yYw&v7>mv8gr~giNj-b5Gm!%uB zS-FgcrNeIYdt!2vL6JoyDxx&nacQ#5XOi5xpr>&W{!cdD04TfgD|E38#c7|<@sU41 z_T*1!*&a{zC9CS~ml*p;PF}AZo}KGv*L<}AT2X)m(DX&BPJeZ`ondl9mRnUK+&&p?#h#8<@)BktPi1`AU zG}>IKg-A*qyZ8lU+mQ$);(-$uWl8c;>Ky}Hw7%O3X<#4|Kc@k>%l5as(mnkt1}AyN zuznp3S-zY@sp=QYRDYaQumX;`g#!IWyQFd>Lj$E;a}>TSm-}W2twH&#ua5Kzv4A)N zlIj4UeHi=;&~Ci0IdOC7p*`hVnP`H!K`M3!F@tDaXI1P|+Ij{#ty!1?mJaNj(>NS8 z%UL&SxPbORE}&hO188UB-{k>-_AU+bgds)$%N~ee2k-%#UgB%Fl~)i$mqSfpnGVKm z%xIF4^rg67G~j@S7+~>9eW3&XxEB=?-j$BdOUy>XdB4;jHkN`q-2p+Y5<5<+C0o4n zXwTM`&v3C|HU&f#^SLEW)xcLsB!S1oY~?H9dfe(c!xG~wQW;?DB< zE>b?Yxfg!(G{CrBiI!isYjJ0X%&gD%0RLIm)_QuZ@e9RyZk2^Qwq=tH7yGHl;_l}v zs(9&WbW9h%=nvbL>8|(pMf}m(o%4AZN$;>v#^IVqY94RGYA;<9w>`;Igu*aXFoi_A+%ElxP*3{jfHq%iyQ$6 z?JpRlTYb&~S+H@VP8M<2lU+g%mP1h+{4Jt5HWWBE5+uK7zaRGxJBBVP)q5KR5ZdWB z-QyfWdoq{M?vW#57O~bfdJovwpk4Pr{35gqW;2G)zVlxnM2#@8){1e!@+6P>lCL?Lu*0Mne227@aEqxv_8xGvUAP_#GB}hy#;P0$}np)|JO6(JzVCdnU4CGv{^1 zRN|etgC9Hq9$$Y!S{Yh^By56}wm(7^Q0S$ok>Y=R_Z zeHd=ud^E?8M%ZG6`7vYjjJyz@Spd9ni&6pHp`TO~H?&{Jo#Gj7$d6 zGA=oYwkK+q1lZer`Z}eyXV+@HFqmocC}9^Tod|L@9mS3fTZcTSh4i-BoeXbWMW3h? z^5hz7*i2{%6V6_cxwnMG#}xoVTuqI9za+zlr;h1#$f6uFGC}94o889->ygCJ!d_*A zO6k@_b=U0)|HC$Js|~oBng`Lml?1M_I+eQ%PNa8#SRZhxi37TgUB~^@y40rBvM)Uw z53(+(rrXWdwB}3~yLtHN8zb@h+}q(_g=CZBQTkfmMRJZmH`H!{`>Xp~~kj ze=;}4TX5|Yx-6Y^iUkFW8VLsOpK%w3C8)90opgEP`Mlb&N7J^0^E5dT%NiM#0ABC( zx0s9#?LjtdYhZKP++>{1gqo|;6|5P2G%$18&vdnIV%kGN;etp3!uxksa8pb)f3wYx T&xفشل التحقق من صحة:' + INVALID_INPUT: 'إدخال غير صحيح في' + MISSING_REQUIRED_FIELD: 'حقل مطلوب مفقود:' + XSS_ISSUES: "مشاكل XSS محتملة تم اكتشافها في حقل '%s' '" + MONTHS_OF_THE_YEAR: + - 'كانون الثاني' + - 'شباط' + - 'آذار/ مارس' + - 'نيسان' + - 'أيار' + - 'حزيران' + - 'تموز' + - 'آب' + - 'أيلول' + - 'تشرين الأول' + - 'تشرين الثاني' + - 'كانون الأول' + DAYS_OF_THE_WEEK: + - 'الاثنين' + - 'الثلاثاء' + - 'الأربعاء' + - 'الخميس' + - 'الجمعة' + - 'السبت' + - 'الأحد' + YES: "نعم" + NO: "لا" + CRON: + EVERY: كل + EVERY_HOUR: كل ساعة + EVERY_MINUTE: كل دقيقة + EVERY_DAY_OF_WEEK: كل يوم في الأسبوع + EVERY_DAY_OF_MONTH: كل يوم في الشهر + EVERY_MONTH: ' كل شهر' + TEXT_PERIOD: كل + TEXT_MINS: ' في دقيقة(دقائق) بعد الساعة' + TEXT_TIME: ' في :' + TEXT_DOW: ' في ' + TEXT_MONTH: ' من ' + TEXT_DOM: ' في ' + ERROR1: الوسم %s غير مدعوم! + ERROR2: عدد عناصر غير صالح. + ERROR4: تعبير غير معروف diff --git a/system/languages/bg.yaml b/system/languages/bg.yaml new file mode 100644 index 0000000..8dc893a --- /dev/null +++ b/system/languages/bg.yaml @@ -0,0 +1,72 @@ +--- +GRAV: + NICETIME: + NO_DATE_PROVIDED: Не е въведена дата + BAD_DATE: Невалидна дата + AGO: преди + FROM_NOW: от сега + JUST_NOW: току що + SECOND: секунда + MINUTE: минута + HOUR: час + DAY: ден + WEEK: седмица + MONTH: месец + YEAR: година + DECADE: десетилетие + SEC: сек + MIN: мин + HR: ч + WK: седм + MO: мес + YR: г + DEC: дстлт + SECOND_PLURAL: секунди + MINUTE_PLURAL: минути + HOUR_PLURAL: часа + DAY_PLURAL: дена + WEEK_PLURAL: седмици + MONTH_PLURAL: месеца + YEAR_PLURAL: години + DECADE_PLURAL: десетилетия + SEC_PLURAL: сек + MIN_PLURAL: мин + HR_PLURAL: ч + WK_PLURAL: седм + MO_PLURAL: мес + YR_PLURAL: г + DEC_PLURAL: дстлт + FORM: + VALIDATION_FAIL: 'Неуспешна проверка:' + INVALID_INPUT: 'Невалидно въвеждане в' + MISSING_REQUIRED_FIELD: 'Липсва задължително поле:' + MONTHS_OF_THE_YEAR: + - 'януари' + - 'февруари' + - 'март' + - 'април' + - 'май' + - 'юни' + - 'юли' + - 'август' + - 'септември' + - 'октомври' + - 'ноември' + - 'декември' + DAYS_OF_THE_WEEK: + - 'понеделник' + - 'вторник' + - 'сряда' + - 'четвъртък' + - 'петък' + - 'събота' + - 'неделя' + YES: "Да" + NO: "Не" + CRON: + EVERY: всеки + EVERY_HOUR: Всеки час + EVERY_MINUTE: Всяка минута + EVERY_DAY_OF_WEEK: Всеки ден от седмицата + EVERY_DAY_OF_MONTH: Всеки ден от месеца + EVERY_MONTH: Всеки месец diff --git a/system/languages/ca.yaml b/system/languages/ca.yaml new file mode 100644 index 0000000..cb2f479 --- /dev/null +++ b/system/languages/ca.yaml @@ -0,0 +1,87 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# S'ha produït un error: Frontmatter invàlid\n\nRuta: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_UNCOUNTABLE: + - '' + - 'informació' + - '' + - '' + - '' + - '' + - '' + - '' + NICETIME: + NO_DATE_PROVIDED: No s'ha proporcionat data + BAD_DATE: Data invàlida + AGO: abans + FROM_NOW: des d'ara + JUST_NOW: Ara mateix + SECOND: segon + MINUTE: minut + HOUR: hora + DAY: dia + WEEK: setmana + MONTH: mes + YEAR: any + DECADE: dècada + SEC: s + HR: h + WK: setm. + MO: m. + YR: a. + DEC: dèc. + SECOND_PLURAL: segons + MINUTE_PLURAL: minuts + HOUR_PLURAL: hores + DAY_PLURAL: dies + WEEK_PLURAL: setmanes + MONTH_PLURAL: mesos + YEAR_PLURAL: anys + DECADE_PLURAL: dècades + SEC_PLURAL: s + MIN_PLURAL: min + HR_PLURAL: h + WK_PLURAL: setm. + MO_PLURAL: mesos + YR_PLURAL: anys + DEC_PLURAL: dèc. + FORM: + VALIDATION_FAIL: 'Ha fallat la validació:' + INVALID_INPUT: 'Entrada no vàlida a' + MISSING_REQUIRED_FIELD: 'Falta camp obligatori:' + XSS_ISSUES: "Detectats potencials problemes XSS al camp '%s'" + MONTHS_OF_THE_YEAR: + - 'Gener' + - 'Febrer' + - 'Març' + - 'Abril' + - 'Maig' + - 'Juny' + - 'Juliol' + - 'Agost' + - 'Setembre' + - 'Octubre' + - 'Novembre' + - 'Desembre' + DAYS_OF_THE_WEEK: + - 'Dilluns' + - 'Dimarts' + - 'Dimecres' + - 'Dijous' + - 'Divendres' + - 'Dissabte' + - 'Diumenge' + YES: "Sí" + NO: "No" + CRON: + EVERY: cada + EVERY_HOUR: cada hora + EVERY_MINUTE: cada minut + EVERY_DAY_OF_WEEK: cada dia de la setmana + EVERY_DAY_OF_MONTH: cada dia del mes + EVERY_MONTH: cada mes + TEXT_PERIOD: Cada + ERROR1: L'etiqueta %s no està suportada! + ERROR2: Nombre d'elements incorrecte + ERROR3: El jquery_element s'ha d'establir a la configuració de jqCron + ERROR4: Expressió no reconeguda diff --git a/system/languages/cs.yaml b/system/languages/cs.yaml new file mode 100644 index 0000000..54aa138 --- /dev/null +++ b/system/languages/cs.yaml @@ -0,0 +1,147 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Chyba: Chybná hlavička\n\nCesta: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_PLURALS: + '/(quiz)$/i': '\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' + INFLECTOR_SINGULAR: + '/(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' + INFLECTOR_UNCOUNTABLE: + - 'vybavení' + - 'informace' + - 'rýže' + - 'peníze' + - 'druhy' + - 'série' + - 'ryba' + - 'ovce' + INFLECTOR_IRREGULAR: + 'person': 'lidé' + 'man': 'muži' + 'child': 'děti' + 'sex': 'pohlaví' + 'move': 'pohyby' + INFLECTOR_ORDINALS: + 'default': '.' + 'first': '.' + 'second': '.' + 'third': '.' + NICETIME: + NO_DATE_PROVIDED: Datum nebylo vloženo + BAD_DATE: Chybné datum + AGO: zpět + FROM_NOW: od teď + JUST_NOW: právě teď + SECOND: sekunda + MINUTE: minuta + HOUR: hodina + DAY: den + WEEK: týden + MONTH: měsíc + YEAR: rok + DECADE: dekáda + SEC: sek + MIN: min + HR: hod + WK: t + MO: m + YR: r + DEC: dek + SECOND_PLURAL: sekundy + MINUTE_PLURAL: minuty + HOUR_PLURAL: hodiny + DAY_PLURAL: dny + WEEK_PLURAL: týdny + MONTH_PLURAL: měsíce + YEAR_PLURAL: roky + DECADE_PLURAL: dekády + SEC_PLURAL: sek + MIN_PLURAL: min + HR_PLURAL: hod + WK_PLURAL: t + MO_PLURAL: m + YR_PLURAL: r + DEC_PLURAL: dek + FORM: + VALIDATION_FAIL: 'Ověření se nezdařilo:' + INVALID_INPUT: 'Neplatný vstup v' + MISSING_REQUIRED_FIELD: 'Chybí požadované pole:' + XSS_ISSUES: "Byly zjištěny možné problémy XSS v poli '%s'" + MONTHS_OF_THE_YEAR: + - 'leden' + - 'únor' + - 'březen' + - 'duben' + - 'květen' + - 'červen' + - 'červenec' + - 'srpen' + - 'září' + - 'říjen' + - 'listopad' + - 'prosinec' + DAYS_OF_THE_WEEK: + - 'pondělí' + - 'úterý' + - 'středa' + - 'čtvrtek' + - 'pátek' + - 'sobota' + - 'neděle' + YES: "Ano" + NO: "Ne" + CRON: + EVERY: každý + EVERY_HOUR: každou hodinu + EVERY_MINUTE: každou minutu + EVERY_DAY_OF_WEEK: každý den v týdnu + EVERY_DAY_OF_MONTH: každý den v měsíci + EVERY_MONTH: každý měsíc + TEXT_PERIOD: Every + TEXT_MINS: ' at minute(s) past the hour' + TEXT_TIME: ' at :' + TEXT_DOW: ' on ' + TEXT_MONTH: ' of ' + TEXT_DOM: ' on ' + ERROR1: Tag %s není podporován! + ERROR2: Chybný počet prvků + ERROR3: jquery_element musí být nastaven v nastaveních pro jqCron + ERROR4: Nerozpoznaný výraz diff --git a/system/languages/da.yaml b/system/languages/da.yaml new file mode 100644 index 0000000..f23477c --- /dev/null +++ b/system/languages/da.yaml @@ -0,0 +1,90 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\nTitel: %1$s\n---\n\n# Fejl: Ugyldigt frontmatter\n\nSti: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_UNCOUNTABLE: + - 'udstyr' + - 'information' + - 'ris' + - 'penge' + - 'arter' + - 'Serier' + - 'fisk' + - 'får' + INFLECTOR_IRREGULAR: + 'person': 'personer' + 'man': 'mænd' + 'child': 'børn' + 'sex': 'køn' + 'move': 'flyt' + NICETIME: + NO_DATE_PROVIDED: Ingen dato angivet + BAD_DATE: Ugyldig dato + AGO: siden + FROM_NOW: fra nu + JUST_NOW: lige nu + SECOND: sekund + MINUTE: minut + HOUR: time + DAY: dag + WEEK: uge + MONTH: måned + YEAR: år + DECADE: årti + SEC: sek + MIN: min. + HR: t + WK: u + MO: md + YR: år + DEC: årti + SECOND_PLURAL: sekunder + MINUTE_PLURAL: minutter + HOUR_PLURAL: timer + DAY_PLURAL: dage + WEEK_PLURAL: uger + MONTH_PLURAL: måneder + YEAR_PLURAL: år + DECADE_PLURAL: årtier + SEC_PLURAL: sek + MIN_PLURAL: min + HR_PLURAL: timer + WK_PLURAL: uger + MO_PLURAL: mdr + YR_PLURAL: år + DEC_PLURAL: årtier + FORM: + VALIDATION_FAIL: 'Validering mislykkedes:' + INVALID_INPUT: 'Ugyldigt input i' + MISSING_REQUIRED_FIELD: 'Mangler obligatorisk felt:' + MONTHS_OF_THE_YEAR: + - 'januar' + - 'februar' + - 'mars' + - 'april' + - 'mai' + - 'juni' + - 'juli' + - 'august' + - 'september' + - 'oktober' + - 'november' + - 'desember' + DAYS_OF_THE_WEEK: + - 'mandag' + - 'tirsdag' + - 'onsdag' + - 'torsdag' + - 'fredag' + - 'lørdag' + - 'søndag' + CRON: + EVERY: hver + EVERY_HOUR: hver time + EVERY_MINUTE: hvert minut + EVERY_DAY_OF_WEEK: alle ugens dage + EVERY_DAY_OF_MONTH: alle dage i måneden + EVERY_MONTH: hver måned + TEXT_PERIOD: Hver + TEXT_MINS: ' ved minut(ter) over timen' + ERROR1: Tagget %s understøttes ikke! + ERROR2: Ugyldigt antal elementer diff --git a/system/languages/de.yaml b/system/languages/de.yaml new file mode 100644 index 0000000..45c24e7 --- /dev/null +++ b/system/languages/de.yaml @@ -0,0 +1,147 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n# Fehler: Frontmatter enthält Fehler\n\nPfad: `%2$s`\n\n**%3$s ** \n\n```\n%4$s\n```" + INFLECTOR_PLURALS: + '/(quiz)$/i': '\1zes' + '/^(ox)$/i': '\1en' + '/([m|l])ouse$/i': '\1ice' + '/(matr|vert|ind)ix|ex$/i': '\1ice' + '/(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' + INFLECTOR_SINGULAR: + '/(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': '\1ies' + '/(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\2ves' + '/([ti])a$/i': '\1um' + '/(n)ews$/i': '\1ews' + INFLECTOR_UNCOUNTABLE: + - 'Ausstattung' + - 'Informationen' + - 'Reis' + - 'Geld' + - 'Arten' + - 'Serie' + - 'Fisch' + - 'Schaf' + INFLECTOR_IRREGULAR: + 'person': 'Personen' + 'man': 'Menschen' + 'child': 'Kinder' + 'sex': 'Geschlecht' + 'move': 'Züge' + INFLECTOR_ORDINALS: + 'default': '.' + 'first': '.' + 'second': '.' + 'third': '.' + NICETIME: + NO_DATE_PROVIDED: Kein Datum angegeben + BAD_DATE: Falsches Datum + AGO: her + FROM_NOW: ab jetzt + JUST_NOW: jetzt gerade + SECOND: Sekunde + MINUTE: Minute + HOUR: Stunde + DAY: Tag + WEEK: Woche + MONTH: Monat + YEAR: Jahr + DECADE: Jahrzehnt + SEC: Sek. + MIN: Min. + HR: Std. + WK: Wo. + MO: Mo. + YR: J. + DEC: Dez + SECOND_PLURAL: Sekunden + MINUTE_PLURAL: Minuten + HOUR_PLURAL: Stunden + DAY_PLURAL: Tage + WEEK_PLURAL: Wochen + MONTH_PLURAL: Monate + YEAR_PLURAL: Jahre + DECADE_PLURAL: Jahrzehnte + SEC_PLURAL: Sekunden + MIN_PLURAL: Minuten + HR_PLURAL: Stunden + WK_PLURAL: Wochen + MO_PLURAL: Monate + YR_PLURAL: Jahre + DEC_PLURAL: Jahrzehnten + FORM: + VALIDATION_FAIL: 'Überprüfung fehlgeschlagen:' + INVALID_INPUT: 'Ungültige Eingabe in' + MISSING_REQUIRED_FIELD: 'Erforderliches Feld fehlt:' + XSS_ISSUES: "Potenzielle XSS-Probleme im Feld '%s' erkannt" + MONTHS_OF_THE_YEAR: + - 'Januar' + - 'Februar' + - 'März' + - 'April' + - 'Mai' + - 'Juni' + - 'Juli' + - 'August' + - 'September' + - 'Oktober' + - 'November' + - 'Dezember' + DAYS_OF_THE_WEEK: + - 'Montag' + - 'Dienstag' + - 'Mittwoch' + - 'Donnerstag' + - 'Freitag' + - 'Samstag' + - 'Sonntag' + YES: "Ja" + NO: "Nein" + CRON: + EVERY: jede + EVERY_HOUR: jede Stunde + EVERY_MINUTE: Jede Minute + EVERY_DAY_OF_WEEK: jeden Tag der Woche + EVERY_DAY_OF_MONTH: jeden Tag des Monats + EVERY_MONTH: jeden Monat + TEXT_PERIOD: Alle + TEXT_MINS: ' bei Minuten nach der vollen Stunde (n)' + TEXT_TIME: ' bei :' + TEXT_DOW: ' auf ' + TEXT_MONTH: ' von ' + TEXT_DOM: ' auf ' + ERROR1: Der Tag %s wird nicht unterstützt! + ERROR2: Ungültige Anzahl von Elementen + ERROR3: jquery_element sollte in den jqCron Einstellungen gesetzt werden + ERROR4: Unbekannter Ausdruck diff --git a/system/languages/el.yaml b/system/languages/el.yaml new file mode 100644 index 0000000..28619f8 --- /dev/null +++ b/system/languages/el.yaml @@ -0,0 +1,144 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\nΤίτλος: %1$s\n---\n\n# Σφάλμα: Μη έγκυρη διαδρομή Frontmatter\n\nΔιαδρομή: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_PLURALS: + '/(quiz)$/i': '\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' + INFLECTOR_SINGULAR: + '/(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' + INFLECTOR_UNCOUNTABLE: + - 'εξοπλισμός' + - 'πληροφοριες' + - 'rice' + - 'χρήματα' + - 'είδη' + - 'σειρές' + - 'ψάρι' + - 'πρόβατο' + INFLECTOR_IRREGULAR: + 'person': 'άνθρωποι' + 'man': 'άνδρες' + 'child': 'παιδιά' + 'sex': 'φύλο' + 'move': 'κινήσεις' + INFLECTOR_ORDINALS: + 'default': 'th' + 'first': 'st' + 'second': 'nd' + 'third': 'rd' + NICETIME: + NO_DATE_PROVIDED: Δεν δόθηκε καμία ημερομηνία + BAD_DATE: Εσφαλμένη ημερομηνία + AGO: πρίν + FROM_NOW: από τώρα + JUST_NOW: μόλις τώρα + SECOND: δευτερόλεπτο + MINUTE: λεπτό + HOUR: ώρα + DAY: ημέρα + WEEK: εβδομάδα + MONTH: μήνας + YEAR: έτος + DECADE: δεκαετία + SEC: δευτερόλεπτο + MIN: λεπτό + HR: ώρα + WK: εβδ + MO: μην + YR: έτος + DEC: δεκαετία + SECOND_PLURAL: δευτερόλεπτα + MINUTE_PLURAL: λεπτά + HOUR_PLURAL: ώρες + DAY_PLURAL: ημέρες + WEEK_PLURAL: εβδομάδες + MONTH_PLURAL: μήνες + YEAR_PLURAL: έτη + DECADE_PLURAL: δεκαετίες + SEC_PLURAL: δευτ. + MIN_PLURAL: λεπτά + HR_PLURAL: ώρες + WK_PLURAL: εβδομάδες + MO_PLURAL: μήνες + YR_PLURAL: έτη + DEC_PLURAL: δεκαετίες + FORM: + VALIDATION_FAIL: 'Η επικύρωση απέτυχε:' + INVALID_INPUT: 'Μη έγκυρα δεδομένα σε' + MISSING_REQUIRED_FIELD: 'Λείπει το απαιτούμενο πεδίο:' + MONTHS_OF_THE_YEAR: + - 'Ιανουάριος' + - 'Φεβρουάριος' + - 'Μάρτιος' + - 'Απρίλιος' + - 'Μάιος' + - 'Ιούνιος' + - 'Ιούλιος' + - 'Αύγουστος' + - 'Σεπτέμβριος' + - 'Οκτώβριος' + - 'Νοέμβριος' + - 'Δεκέμβριος' + DAYS_OF_THE_WEEK: + - 'Δευτέρα' + - 'Τρίτη' + - 'Τετάρτη' + - 'Πέμπτη' + - 'Παρασκευή' + - 'Σάββατο' + - 'Κυριακή' + CRON: + EVERY: κάθε + EVERY_HOUR: κάθε ώρα + EVERY_MINUTE: κάθε λεπτό + EVERY_DAY_OF_WEEK: κάθε μέρα της εβδομάδος + EVERY_DAY_OF_MONTH: κάθε μέρα του μήνα + EVERY_MONTH: κάθε μήνα + TEXT_PERIOD: Κάθε + TEXT_MINS: ' κατά λεπτό(ά) μετά την ώρα' + TEXT_TIME: ' στο :' + TEXT_DOW: ' στις ' + TEXT_MONTH: ' από ' + TEXT_DOM: ' στις ' + ERROR1: Η ετικέτα %s δεν υποστηρίζεται! + ERROR2: Μη έγκυρος αριθμός στοιχείων + ERROR3: Το jquery_element θα έπρεπε να οριστεί στις ρυθμίσεις του jqCron + ERROR4: Μη αναγνωρισμένη έκφραση diff --git a/system/languages/en.yaml b/system/languages/en.yaml new file mode 100644 index 0000000..7a8a68c --- /dev/null +++ b/system/languages/en.yaml @@ -0,0 +1,121 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Error: Invalid Frontmatter\n\nPath: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_PLURALS: + '/(quiz)$/i': '\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' + INFLECTOR_SINGULAR: + '/(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': '' + INFLECTOR_UNCOUNTABLE: ['equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep'] + INFLECTOR_IRREGULAR: + 'person': 'people' + 'man': 'men' + 'child': 'children' + 'sex': 'sexes' + 'move': 'moves' + INFLECTOR_ORDINALS: + 'default': 'th' + 'first': 'st' + 'second': 'nd' + 'third': 'rd' + NICETIME: + NO_DATE_PROVIDED: No date provided + BAD_DATE: Bad date + AGO: ago + FROM_NOW: from now + JUST_NOW: just now + SECOND: second + MINUTE: minute + HOUR: hour + DAY: day + WEEK: week + MONTH: month + YEAR: year + DECADE: decade + SEC: sec + MIN: min + HR: hr + WK: wk + MO: mo + YR: yr + DEC: dec + SECOND_PLURAL: seconds + MINUTE_PLURAL: minutes + HOUR_PLURAL: hours + DAY_PLURAL: days + WEEK_PLURAL: weeks + MONTH_PLURAL: months + YEAR_PLURAL: years + DECADE_PLURAL: decades + SEC_PLURAL: secs + MIN_PLURAL: mins + HR_PLURAL: hrs + WK_PLURAL: wks + MO_PLURAL: mos + YR_PLURAL: yrs + DEC_PLURAL: decs + FORM: + VALIDATION_FAIL: 'Validation failed:' + INVALID_INPUT: 'Invalid input in' + MISSING_REQUIRED_FIELD: 'Missing required field:' + XSS_ISSUES: "Potential XSS issues detected in '%s' field" + MONTHS_OF_THE_YEAR: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] + DAYS_OF_THE_WEEK: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] + YES: "Yes" + NO: "No" + CRON: + EVERY: every + EVERY_HOUR: every hour + EVERY_MINUTE: every minute + EVERY_DAY_OF_WEEK: every day of the week + EVERY_DAY_OF_MONTH: every day of the month + EVERY_MONTH: every month + TEXT_PERIOD: Every + TEXT_MINS: ' at minute(s) past the hour' + TEXT_TIME: ' at :' + TEXT_DOW: ' on ' + TEXT_MONTH: ' of ' + TEXT_DOM: ' on ' + ERROR1: The tag %s is not supported! + ERROR2: Bad number of elements + ERROR3: The jquery_element should be set into jqCron settings + ERROR4: Unrecognized expression diff --git a/system/languages/eo.yaml b/system/languages/eo.yaml new file mode 100644 index 0000000..0da621f --- /dev/null +++ b/system/languages/eo.yaml @@ -0,0 +1,40 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Eraro: Nevalida Frontmatter\n\nVojo: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_PLURALS: + '/sis$/i': 'j' + NICETIME: + FROM_NOW: ekde nun + JUST_NOW: Ĝuste nun + SECOND: sekundo + MINUTE: minuto + HOUR: horo + DAY: tago + WEEK: semajno + MONTH: monato + YEAR: jaro + DECADE: jardeko + SEC: sek. + MIN: min. + HR: horo + SECOND_PLURAL: sekundoj + MINUTE_PLURAL: minutoj + HOUR_PLURAL: horoj + DAY_PLURAL: tagoj + WEEK_PLURAL: semajnoj + MONTH_PLURAL: monatoj + YEAR_PLURAL: jaroj + DECADE_PLURAL: jardekoj + MONTHS_OF_THE_YEAR: + - 'januaro' + - 'februaro' + - 'marto' + - 'aprilo' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' diff --git a/system/languages/es.yaml b/system/languages/es.yaml new file mode 100644 index 0000000..3786565 --- /dev/null +++ b/system/languages/es.yaml @@ -0,0 +1,107 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\ntítulo: %1$s\n---\n\n# Error: Prefacio no válido\n\nRuta: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_PLURALS: + '/(quiz)$/i': '\1ios' + '/s$/i': 's' + '/$/': 's' + INFLECTOR_UNCOUNTABLE: + - 'equipamiento' + - 'información' + - 'arroz' + - 'dinero' + - 'especies' + - 'series' + - 'pescado' + - 'oveja' + INFLECTOR_IRREGULAR: + 'person': 'personas' + 'man': 'hombres' + 'child': 'niños' + 'sex': 'sexos' + 'move': 'movido' + INFLECTOR_ORDINALS: + 'first': '.º' + 'second': '.º' + 'third': '.º' + NICETIME: + NO_DATE_PROVIDED: No se proporcionó fecha + BAD_DATE: Fecha errónea + AGO: antes + FROM_NOW: desde ahora + JUST_NOW: hace un momento + SECOND: segundo + MINUTE: minuto + HOUR: hora + DAY: día + WEEK: semana + MONTH: mes + YEAR: año + DECADE: década + SEC: seg + MIN: min + HR: h + WK: sem + MO: mes + YR: año + DEC: déc + SECOND_PLURAL: segundos + MINUTE_PLURAL: minutos + HOUR_PLURAL: horas + DAY_PLURAL: días + WEEK_PLURAL: semanas + MONTH_PLURAL: meses + YEAR_PLURAL: años + DECADE_PLURAL: décadas + SEC_PLURAL: segs + MIN_PLURAL: mins + HR_PLURAL: hs + WK_PLURAL: sem + MO_PLURAL: mes + YR_PLURAL: años + DEC_PLURAL: décadas + FORM: + VALIDATION_FAIL: 'Falló la validación: ' + INVALID_INPUT: 'Dato inválido en: ' + MISSING_REQUIRED_FIELD: 'Falta el campo requerido: ' + XSS_ISSUES: "Se detectaron potenciales problemas XSS en el campo '%s'" + MONTHS_OF_THE_YEAR: + - 'Enero' + - 'Febrero' + - 'Marzo' + - 'Abril' + - 'Mayo' + - 'Junio' + - 'Julio' + - 'Agosto' + - 'Septiembre' + - 'Octubre' + - 'Noviembre' + - 'Diciembre' + DAYS_OF_THE_WEEK: + - 'Lunes' + - 'Martes' + - 'Miércoles' + - 'Jueves' + - 'Viernes' + - 'Sábado' + - 'Domingo' + YES: "Sí" + NO: "No" + CRON: + EVERY: cada + EVERY_HOUR: cada hora + EVERY_MINUTE: cada minuto + EVERY_DAY_OF_WEEK: cada día de la semana + EVERY_DAY_OF_MONTH: cada día del mes + EVERY_MONTH: cada mes + TEXT_PERIOD: Cada + TEXT_MINS: ' a minuto(s) después de la hora' + TEXT_TIME: ' a :' + TEXT_DOW: ' en ' + TEXT_MONTH: ' de' + TEXT_DOM: ' en' + ERROR1: No se admite la etiqueta %s. + ERROR2: El número de elementos es erróneo + ERROR3: El jquery_element debería establecerse en la configuración del jqCron + ERROR4: Expresión no reconocida diff --git a/system/languages/et.yaml b/system/languages/et.yaml new file mode 100644 index 0000000..2bc1920 --- /dev/null +++ b/system/languages/et.yaml @@ -0,0 +1,108 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\npealkiri: %1$s\n---\n\n# Viga: vigane Frontmatter'i\n\nasukoht: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_PLURALS: + '/(octop|vir)us$/i': '\1i' + INFLECTOR_SINGULAR: + '/^(ox)en/i': '\1' + '/(alias|status)es$/i': '\1' + '/(shoe)s$/i': '\1' + '/(o)es$/i': '\1' + '/(bus)es$/i': '\1' + '/(x|ch|ss|sh)es$/i': '\1' + '/(tive)s$/i': '\1' + '/(hive)s$/i': '\1' + INFLECTOR_UNCOUNTABLE: + - '' + - 'informatsioon' + - 'riis' + - 'raha' + - '' + - '' + - 'kala' + - 'lammas' + INFLECTOR_IRREGULAR: + 'person': 'inimesed' + 'man': 'mees' + 'child': 'lapsed' + INFLECTOR_ORDINALS: + 'default': '.' + 'first': '.' + 'second': '.' + 'third': '.' + NICETIME: + NO_DATE_PROVIDED: Kuupäev määramata + BAD_DATE: Vigane kuupäev + AGO: tagasi + FROM_NOW: praegusest + JUST_NOW: just nüüd + SECOND: sekund + MINUTE: minut + HOUR: tundi + DAY: päev + WEEK: nädal + MONTH: kuu + YEAR: aasta + DECADE: 10 aastat + SEC: sek + MIN: min + HR: t + WK: näd + MO: k. + YR: a. + DEC: dekaad + SECOND_PLURAL: sekundit + MINUTE_PLURAL: minutit + HOUR_PLURAL: tundi + DAY_PLURAL: päeva + WEEK_PLURAL: nädalat + MONTH_PLURAL: kuud + YEAR_PLURAL: aastat + DECADE_PLURAL: dekaadi + SEC_PLURAL: sekundit + MIN_PLURAL: min + HR_PLURAL: t + WK_PLURAL: näd + MO_PLURAL: kuud + YR_PLURAL: aastat + DEC_PLURAL: dek. + FORM: + VALIDATION_FAIL: 'Kinnitamine nurjus:' + INVALID_INPUT: 'Vigane sisend:' + MISSING_REQUIRED_FIELD: 'Nõutud väli puudub:' + XSS_ISSUES: "Tuvastasime '%s' väljal võimaliku XSS-riski" + MONTHS_OF_THE_YEAR: + - 'jaanuar' + - 'veebruar' + - 'märts' + - 'aprill' + - 'mai' + - 'juuni' + - 'juuli' + - 'august' + - 'september' + - 'oktoober' + - 'november' + - 'detsember' + DAYS_OF_THE_WEEK: + - 'esmaspäev' + - 'teisipäev' + - 'kolmapäev' + - 'neljapäev' + - 'reede' + - 'laupäev' + - 'pühapäev' + YES: "Jah" + NO: "Ei" + CRON: + EVERY: iga + EVERY_HOUR: iga tund + EVERY_MINUTE: iga minut + EVERY_DAY_OF_WEEK: nädala igal päeval + EVERY_DAY_OF_MONTH: kuu igal päeval + EVERY_MONTH: iga kuu + TEXT_PERIOD: Iga + ERROR1: Silt %s pole toetatud! + ERROR2: Vale elementide arv + ERROR3: jqCron seadetes peaks olema määratud jquery_element + ERROR4: Tundmatu väljend diff --git a/system/languages/eu.yaml b/system/languages/eu.yaml new file mode 100644 index 0000000..91c3c8e --- /dev/null +++ b/system/languages/eu.yaml @@ -0,0 +1,62 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "--- title: %1$s --- # Errorea: Baliogabeko Frontmatter Bidea: `%2$s` **%3$s** ``` %4$s ```" + NICETIME: + NO_DATE_PROVIDED: Ez da datarik ezarri + BAD_DATE: Okerreko data + AGO: ' duela' + FROM_NOW: oraindik aurrera + SECOND: segundo + MINUTE: minutu + HOUR: ordua + DAY: egun + WEEK: astea + MONTH: hilabetea + YEAR: urtea + DECADE: hamarkada + SEC: seg + HR: h + WK: ast + MO: hil + YR: urt + DEC: ham + SECOND_PLURAL: segundo + MINUTE_PLURAL: minutu + HOUR_PLURAL: ordu + DAY_PLURAL: egun + WEEK_PLURAL: aste + MONTH_PLURAL: hilabete + YEAR_PLURAL: urte + DECADE_PLURAL: hamarkada + SEC_PLURAL: segundo + MIN_PLURAL: minutu + HR_PLURAL: h + WK_PLURAL: ast + MO_PLURAL: hil + YR_PLURAL: urt + DEC_PLURAL: ham + FORM: + VALIDATION_FAIL: 'Balidazioak huts egin du' + INVALID_INPUT: 'Baliogabeko sarrera' + MISSING_REQUIRED_FIELD: 'Derrigorrezko eremua bete gabe:' + MONTHS_OF_THE_YEAR: + - 'Urtarrila' + - 'Otsaila' + - 'Martxoa' + - 'Apirila' + - 'Maiatza' + - 'Ekaina' + - 'Uztaila' + - 'Abuztua' + - 'Iraila' + - 'Urria' + - 'Azaroa' + - 'Abendua' + DAYS_OF_THE_WEEK: + - 'Astelehena' + - 'Asteartea' + - 'Azteazkena' + - 'Osteguna' + - 'Ostirala' + - 'Larunbata' + - 'Igandea' diff --git a/system/languages/fa.yaml b/system/languages/fa.yaml new file mode 100644 index 0000000..96b96dc --- /dev/null +++ b/system/languages/fa.yaml @@ -0,0 +1,62 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\nعنوان: %1$s\n---\n\n# خطا: Frontmatter غلط\n\nمسیر: %2$s\n\n**%3$s**\n\n```\n%4$s\n```" + NICETIME: + NO_DATE_PROVIDED: تاریخی ارائه نشده + BAD_DATE: تاریخ اشتباه + AGO: قبل + FROM_NOW: از حالا + SECOND: ثانیه + MINUTE: دقیقه + HOUR: ساعت + DAY: روز + WEEK: هفته + MONTH: ماه + YEAR: سال + DECADE: دهه + SEC: ثانیه + MIN: دقیقه + HR: ساعت + WK: هفته + MO: ماه + YR: سال + DEC: دهه + SECOND_PLURAL: ثانیه + MINUTE_PLURAL: دقیقه + HOUR_PLURAL: ساعت + DAY_PLURAL: روز + WEEK_PLURAL: هفته + MONTH_PLURAL: ماه + YEAR_PLURAL: سال + DECADE_PLURAL: دهه + SEC_PLURAL: ثانیه + MIN_PLURAL: دقیقه + HR_PLURAL: ساعت + WK_PLURAL: هفته + YR_PLURAL: سال + DEC_PLURAL: دهه + FORM: + VALIDATION_FAIL: 'سنجش اعتبار ناموفق بود' + INVALID_INPUT: 'ورودی نامعتبر در' + MISSING_REQUIRED_FIELD: 'قسمت ضروری جا افتاده:' + MONTHS_OF_THE_YEAR: + - 'ژانویه' + - 'فوریه' + - 'مارس' + - 'آوریل' + - 'می' + - 'ژوئن' + - 'ژوئیه' + - 'اوت' + - 'سپتامبر' + - 'اکتبر' + - 'نوامبر' + - 'دسامبر' + DAYS_OF_THE_WEEK: + - 'دوشنبه' + - 'سه‌ شنبه' + - 'چهارشنبه' + - 'پنج شنبه' + - 'جمعه' + - 'شنبه' + - 'یک‌شنبه' diff --git a/system/languages/fi.yaml b/system/languages/fi.yaml new file mode 100644 index 0000000..d0513fa --- /dev/null +++ b/system/languages/fi.yaml @@ -0,0 +1,134 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\notsikko: %1$s\n---\n\n# Virhe: Virheellinen Frontmatter\n\nPolku: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_PLURALS: + '/(quiz)$/i': '\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' + INFLECTOR_SINGULAR: + '/(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' + INFLECTOR_UNCOUNTABLE: + - '' + - '' + - 'riisi' + - 'raha' + - 'lajit' + - '' + - 'kala' + - 'lammas' + INFLECTOR_IRREGULAR: + 'person': 'ihmiset' + 'man': 'miehet' + 'child': 'lapset' + 'sex': 'sukupuoli' + INFLECTOR_ORDINALS: + 'default': '.' + 'first': '.' + 'second': '.' + 'third': '.' + NICETIME: + NO_DATE_PROVIDED: Päivämäärää ei annettu + BAD_DATE: Virheellinen päivämäärä + AGO: sitten + FROM_NOW: tästä lähtien + JUST_NOW: juuri nyt + SECOND: sekuntti + MINUTE: minuutti + HOUR: tunti + DAY: päivä + WEEK: viikko + MONTH: kuukausi + YEAR: vuosi + DECADE: vuosikymmen + SEC: sek + MIN: min + HR: h + WK: vk + MO: kk + YR: v + DEC: vuosikymmen + SECOND_PLURAL: sekuntia + MINUTE_PLURAL: minuuttia + HOUR_PLURAL: tuntia + DAY_PLURAL: päivää + WEEK_PLURAL: viikkoa + MONTH_PLURAL: kuukautta + YEAR_PLURAL: vuotta + DECADE_PLURAL: vuosikymmentä + SEC_PLURAL: sek + MIN_PLURAL: min + HR_PLURAL: h + WK_PLURAL: v + MO_PLURAL: kk + YR_PLURAL: v + DEC_PLURAL: vuosikymmentä + FORM: + VALIDATION_FAIL: 'Vahvistus epäonnistui:' + INVALID_INPUT: 'Syöte ei kelpaa' + MISSING_REQUIRED_FIELD: 'Puuttuva pakollinen kenttä:' + MONTHS_OF_THE_YEAR: + - 'Tammikuu' + - 'Helmikuu' + - 'Maaliskuu' + - 'Huhtikuu' + - 'Toukokuu' + - 'Kesäkuuta' + - 'Heinäkuu' + - 'Elokuu' + - 'Syyskuu' + - 'Lokakuu' + - 'Marraskuu' + - 'Joulukuu' + DAYS_OF_THE_WEEK: + - 'Maanantai' + - 'Tiistai' + - 'Keskiviikko' + - 'Torstai' + - 'Perjantai' + - 'Lauantai' + - 'Sunnuntai' + CRON: + EVERY: joka + EVERY_HOUR: joka tunti + EVERY_MINUTE: joka minuutti + EVERY_DAY_OF_WEEK: viikon jokaisena päivänä + EVERY_DAY_OF_MONTH: kuukauden jokaisena päivänä + EVERY_MONTH: joka kuukausi + TEXT_PERIOD: Joka diff --git a/system/languages/fr.yaml b/system/languages/fr.yaml new file mode 100644 index 0000000..edf7d76 --- /dev/null +++ b/system/languages/fr.yaml @@ -0,0 +1,147 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\ntitre: %1$s\n---\n\n# Erreur : Frontmatter invalide\n\nChemin: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_PLURALS: + '/(quiz)$/i': '\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': '\1es' + '/(bu)s$/i': 'Bus' + '/(alias|status)/i': 'alias|status' + '/(octop|vir)us$/i': 'virus' + '/(ax|test)is$/i': '\1s' + '/s$/i': 's' + '/$/': 's' + INFLECTOR_SINGULAR: + '/(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': '\1ouvelles' + INFLECTOR_UNCOUNTABLE: + - 'équipement' + - 'information' + - 'riz' + - 'argent' + - 'espèces' + - 'séries' + - 'poisson' + - 'mouton' + INFLECTOR_IRREGULAR: + 'person': 'personnes' + 'man': 'hommes' + 'child': 'enfants' + 'sex': 'sexes' + 'move': 'déplacements' + INFLECTOR_ORDINALS: + 'default': 'ème' + 'first': 'er' + 'second': 'ème' + 'third': 'ème' + NICETIME: + NO_DATE_PROVIDED: Aucune date fournie + BAD_DATE: Date erronée + AGO: plus tôt + FROM_NOW: à partir de maintenant + JUST_NOW: à l'instant + SECOND: seconde + MINUTE: minute + HOUR: heure + DAY: jour + WEEK: semaine + MONTH: mois + YEAR: année + DECADE: décennie + SEC: sec. + MIN: min. + HR: hr. + WK: sem. + MO: m + YR: an + DEC: déc + SECOND_PLURAL: secondes + MINUTE_PLURAL: minutes + HOUR_PLURAL: heures + DAY_PLURAL: jours + WEEK_PLURAL: semaines + MONTH_PLURAL: mois + YEAR_PLURAL: années + DECADE_PLURAL: décennies + SEC_PLURAL: s + MIN_PLURAL: m + HR_PLURAL: h + WK_PLURAL: sem + MO_PLURAL: mois + YR_PLURAL: a + DEC_PLURAL: décs + FORM: + VALIDATION_FAIL: 'La validation a échoué :' + INVALID_INPUT: 'Saisie non valide' + MISSING_REQUIRED_FIELD: 'Champ obligatoire manquant :' + XSS_ISSUES: "Erreurs XSS probablement détectées dans le champ '%s'" + MONTHS_OF_THE_YEAR: + - 'janvier' + - 'février' + - 'mars' + - 'avril' + - 'mai' + - 'juin' + - 'juillet' + - 'août' + - 'septembre' + - 'octobre' + - 'novembre' + - 'décembre' + DAYS_OF_THE_WEEK: + - 'lundi' + - 'mardi' + - 'mercredi' + - 'jeudi' + - 'vendredi' + - 'samedi' + - 'dimanche' + YES: "Oui" + NO: "Non" + CRON: + EVERY: chaque + EVERY_HOUR: toutes les heures + EVERY_MINUTE: chaque minute + EVERY_DAY_OF_WEEK: tous les jours de la semaine + EVERY_DAY_OF_MONTH: tous les jours du mois + EVERY_MONTH: chaque mois + TEXT_PERIOD: Chaque + TEXT_MINS: ' à minute(s) après l''heure' + TEXT_TIME: ' à:' + TEXT_DOW: ' sur ' + TEXT_MONTH: ' de ' + TEXT_DOM: ' sur ' + ERROR1: La balise %s n'est pas prise en charge ! + ERROR2: Nombre invalide d'éléments + ERROR3: L'élément jquery_element doit être défini dans les paramètres jqCron + ERROR4: Expression non reconnue diff --git a/system/languages/gl.yaml b/system/languages/gl.yaml new file mode 100644 index 0000000..b9e581f --- /dev/null +++ b/system/languages/gl.yaml @@ -0,0 +1,147 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\ntítulo: %1$s\n---\n\n# Erro: Limiar incorrecto\n\nRuta: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_PLURALS: + '/(quiz)$/i': '\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': '\1' + '/(octop|vir)us$/i': '\1' + '/(ax|test)is$/i': '\1es' + '/s$/i': 's' + '/$/': 's' + INFLECTOR_SINGULAR: + '/(quiz)zes$/i': '\1ces' + '/(matr)ices$/i': '\1ix' + '/(vert|ind)ices$/i': '\1ex' + '/^(ox)en/i': '\1' + '/(alias|status)es$/i': '\1' + '/([octop|vir])i$/i': '\1' + '/(cris|ax|test)es$/i': '\1es' + '/(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': '\1' + '/([lr])ves$/i': '\1f' + '/(tive)s$/i': '\1' + '/(hive)s$/i': '\1' + '/([^f])ves$/i': '\1fe' + '/(^analy)ses$/i': '\1se' + '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i': '\1\2se' + '/([ti])a$/i': '\1um' + '/(n)ews$/i': '\1ews' + INFLECTOR_UNCOUNTABLE: + - 'equipo' + - 'información' + - 'arroz' + - 'diñeiro' + - 'especies' + - 'series' + - 'peixe' + - 'ovella' + INFLECTOR_IRREGULAR: + 'person': 'xente' + 'man': 'home' + 'child': 'neno' + 'sex': 'sexos' + 'move': 'move' + INFLECTOR_ORDINALS: + 'default': 'º' + 'first': 'º' + 'second': 'º' + 'third': 'º' + NICETIME: + NO_DATE_PROVIDED: Non fornece unha data + BAD_DATE: Data errada + AGO: hai + FROM_NOW: dende agora + JUST_NOW: xusto agora + SECOND: segundo + MINUTE: minuto + HOUR: hora + DAY: día + WEEK: semana + MONTH: mes + YEAR: ano + DECADE: década + SEC: seg + MIN: min + HR: hr + WK: Sem + MO: m + YR: a + DEC: dec + SECOND_PLURAL: segundos + MINUTE_PLURAL: minutos + HOUR_PLURAL: horas + DAY_PLURAL: días + WEEK_PLURAL: semanas + MONTH_PLURAL: meses + YEAR_PLURAL: anos + DECADE_PLURAL: décadas + SEC_PLURAL: segs + MIN_PLURAL: mins + HR_PLURAL: hrs + WK_PLURAL: sem + MO_PLURAL: mes + YR_PLURAL: a + DEC_PLURAL: deca + FORM: + VALIDATION_FAIL: 'Fallou a validación:' + INVALID_INPUT: 'Entrada incorrecta en' + MISSING_REQUIRED_FIELD: 'Falta un campo requirido:' + XSS_ISSUES: "Detectáronse posibles problemas XSS no campo '% s'" + MONTHS_OF_THE_YEAR: + - 'xaneiro' + - 'febreiro' + - 'marzo' + - 'abril' + - 'maio' + - 'xuño' + - 'xullo' + - 'agosto' + - 'setembro' + - 'outubro' + - 'novembro' + - 'decembro' + DAYS_OF_THE_WEEK: + - 'luns' + - 'martes' + - 'mércores' + - 'xoves' + - 'venres' + - 'sábado' + - 'domingo' + YES: "Si" + NO: "Non" + CRON: + EVERY: cada + EVERY_HOUR: Cada hora + EVERY_MINUTE: Cada minuto + EVERY_DAY_OF_WEEK: cada día da semana + EVERY_DAY_OF_MONTH: cada día do mes + EVERY_MONTH: cada mes + TEXT_PERIOD: Cada + TEXT_MINS: ' dentro de minuto(s) despois da hora' + TEXT_TIME: ' dentro :' + TEXT_DOW: ' o ' + TEXT_MONTH: ' de ' + TEXT_DOM: ' o ' + ERROR1: A etiqueta %s non é compatíbel! + ERROR2: Mal número de elementos + ERROR3: O jquery_element debería estar determinado na configuración de jqCron + ERROR4: Expresión non recoñecida diff --git a/system/languages/he.yaml b/system/languages/he.yaml new file mode 100644 index 0000000..25e0399 --- /dev/null +++ b/system/languages/he.yaml @@ -0,0 +1,99 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\nכותרת: %1$s\n---\n# שגיאה: Fronmatter לא חוקי\nנתיב: `%2$s`\n**%3$s**\n```\n%4$s\n```" + INFLECTOR_UNCOUNTABLE: + - 'ציוד' + - 'מידע' + - 'אורז' + - 'כסף' + - 'מינים' + - 'סדרה' + - 'דג' + - 'כבשה' + INFLECTOR_IRREGULAR: + 'person': 'אנשים' + 'man': 'גברים' + 'child': 'ילדים' + 'sex': 'מינים' + 'move': 'מהלכים' + NICETIME: + NO_DATE_PROVIDED: לא סופק תאריך + BAD_DATE: תאריך פגום + AGO: לפני + FROM_NOW: כרגע + JUST_NOW: כרגע + SECOND: שנייה + MINUTE: דקה + HOUR: שעה + DAY: יום + WEEK: שבוע + MONTH: חודש + YEAR: שנה + DECADE: עשור + SEC: שנ' + MIN: דק' + HR: ש' + WK: שב' + MO: חו' + YR: שני' + DEC: עש' + SECOND_PLURAL: שניות + MINUTE_PLURAL: דקות + HOUR_PLURAL: שעות + DAY_PLURAL: ימים + WEEK_PLURAL: שבועות + MONTH_PLURAL: חודשים + YEAR_PLURAL: שנים + DECADE_PLURAL: עשורים + SEC_PLURAL: שנ' + MIN_PLURAL: דק' + HR_PLURAL: ש' + WK_PLURAL: שב' + MO_PLURAL: חו' + YR_PLURAL: שני' + DEC_PLURAL: עש' + FORM: + VALIDATION_FAIL: 'האימות נכשל:' + INVALID_INPUT: 'קלט לא חוקי' + MISSING_REQUIRED_FIELD: 'שדות חובה חסרים:' + XSS_ISSUES: "בעיות XSS פוטנציאליות זוהו בשדה '%s'" + MONTHS_OF_THE_YEAR: + - 'ינואר' + - 'פברואר' + - 'מרץ' + - 'אפריל' + - 'מאי' + - 'יוני' + - 'יולי' + - 'אוגוסט' + - 'ספטמבר' + - 'אוקטובר' + - 'נובמבר' + - 'דצמבר' + DAYS_OF_THE_WEEK: + - 'שני' + - 'שלישי' + - 'רביעי' + - 'חמישי' + - 'שישי' + - 'שבת' + - 'ראשון' + YES: "כן" + NO: "לא" + CRON: + EVERY: בכל + EVERY_HOUR: בכל שעה + EVERY_MINUTE: כל דקה + EVERY_DAY_OF_WEEK: כל יום בשבוע + EVERY_DAY_OF_MONTH: בכל יום בחודש + EVERY_MONTH: כל חודש + TEXT_PERIOD: כל + TEXT_MINS: 'ב דקות אחרי השעה' + TEXT_TIME: 'ב :' + TEXT_DOW: 'ב ' + TEXT_MONTH: 'של ' + TEXT_DOM: 'ב ' + ERROR1: התגית %s אינו נתמכת + ERROR2: מספר לא חוקי של משתנים. + ERROR3: יש להגדיר את ה-jquery_element להגדרות jqCron + ERROR4: ביטוי לא מזוהה diff --git a/system/languages/hr.yaml b/system/languages/hr.yaml new file mode 100644 index 0000000..31b4154 --- /dev/null +++ b/system/languages/hr.yaml @@ -0,0 +1,104 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\nnaslov: %1$s\n---\n\n# Pogreška: nevažeći frontmatter\n\nPutanja datoteke: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_UNCOUNTABLE: + - 'oprema' + - 'informacija' + - 'riža' + - 'novac' + - 'vrsta' + - 'serija' + - 'riba' + - 'ovca' + INFLECTOR_IRREGULAR: + 'person': 'osobe' + 'man': 'ljudi' + 'child': 'djeca' + 'sex': 'spolovi' + 'move': 'Pomakni' + INFLECTOR_ORDINALS: + 'default': '.' + 'first': '.' + 'second': '.' + 'third': '.' + NICETIME: + NO_DATE_PROVIDED: Datum nije upisan + BAD_DATE: Pogrešan datum + AGO: prije + FROM_NOW: od sada + JUST_NOW: upravo sad + SECOND: sekunda + MINUTE: minuta + HOUR: sat + DAY: dan + WEEK: tjedan + MONTH: mjesec + YEAR: godina + DECADE: desetljeće + SEC: sek + MIN: min + HR: sat + WK: t + MO: m + YR: g + DEC: des + SECOND_PLURAL: sekundi + MINUTE_PLURAL: minuta + HOUR_PLURAL: sati + DAY_PLURAL: dan + WEEK_PLURAL: tjedana + MONTH_PLURAL: mjeseci + YEAR_PLURAL: godina + DECADE_PLURAL: desetljeća + SEC_PLURAL: sek + MIN_PLURAL: min + HR_PLURAL: sat + WK_PLURAL: t + MO_PLURAL: m + YR_PLURAL: g + DEC_PLURAL: des + FORM: + VALIDATION_FAIL: 'Validacija nije uspjela:' + INVALID_INPUT: 'Pogrešan unos u' + MISSING_REQUIRED_FIELD: 'Nedostaje obavezno polje:' + XSS_ISSUES: "Potencijalni XSS problemi otkriveni u polju '%s'" + MONTHS_OF_THE_YEAR: + - 'Siječanj' + - 'Veljača' + - 'Ožujak' + - 'Travanj' + - 'Svibanj' + - 'Lipanj' + - 'Srpanj' + - 'Kolovoz' + - 'Rujan' + - 'Listopad' + - 'Studeni' + - 'Prosinac' + DAYS_OF_THE_WEEK: + - 'Ponedjeljak' + - 'Utorak' + - 'Srijeda' + - 'Četvrtak' + - 'Petak' + - 'Subota' + - 'Nedjelja' + YES: "Da" + NO: "Ne" + CRON: + EVERY: svaki + EVERY_HOUR: svaki sat + EVERY_MINUTE: svake minute + EVERY_DAY_OF_WEEK: svaki dan u tjednu + EVERY_DAY_OF_MONTH: svaki dan u mjesecu + EVERY_MONTH: svaki mjesec + TEXT_PERIOD: Svakih + TEXT_MINS: ' u minut(e) nakon sata' + TEXT_TIME: ' u :' + TEXT_DOW: ' na ' + TEXT_MONTH: ' ' + TEXT_DOM: ' na ' + ERROR1: Oznaka %s nije podržana! + ERROR2: Pogrešan broj elemenata. + ERROR3: jquery_element treba postaviti u postavke jqCron + ERROR4: Izraz nije prepoznat diff --git a/system/languages/hu.yaml b/system/languages/hu.yaml new file mode 100644 index 0000000..2624cf9 --- /dev/null +++ b/system/languages/hu.yaml @@ -0,0 +1,97 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\ncím: %1$s\n---\n\n# Hiba: Érvénytelen Frontmatter\n\nElérési út: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_UNCOUNTABLE: + - 'felszerelés' + - 'információ' + - 'rizs' + - 'pénz' + - 'fajok' + - 'sorozat' + - 'hal' + - 'juh' + INFLECTOR_IRREGULAR: + 'person': 'személyek' + 'man': 'férfiak' + 'child': 'gyerekek' + 'sex': 'nemek' + 'move': 'lépések' + INFLECTOR_ORDINALS: + 'default': '.' + 'first': '.' + 'second': '.' + 'third': '.' + NICETIME: + NO_DATE_PROVIDED: Nincs dátum megadva + BAD_DATE: Hibás dátum + AGO: elteltével + FROM_NOW: mostantól + JUST_NOW: épp most + SECOND: másodperc + MINUTE: perc + HOUR: óra + DAY: nap + WEEK: hét + MONTH: hónap + YEAR: év + DECADE: évtized + SEC: mp + MIN: p + HR: ó + WK: hét + MO: hó + YR: év + DEC: évt + SECOND_PLURAL: másodperc + MINUTE_PLURAL: perc + HOUR_PLURAL: óra + DAY_PLURAL: nap + WEEK_PLURAL: hét + MONTH_PLURAL: hónap + YEAR_PLURAL: év + DECADE_PLURAL: évtized + SEC_PLURAL: mp + MIN_PLURAL: perc + HR_PLURAL: ó + WK_PLURAL: hét + MO_PLURAL: hó + YR_PLURAL: év + DEC_PLURAL: évt + FORM: + VALIDATION_FAIL: 'Érvényesítés nem sikerült:' + INVALID_INPUT: 'A megadott érték érvénytelen:' + MISSING_REQUIRED_FIELD: 'Ez a kötelező mező nincs kitöltve:' + MONTHS_OF_THE_YEAR: + - 'január' + - 'február' + - 'március' + - 'április' + - 'május' + - 'június' + - 'július' + - 'augusztus' + - 'szeptember' + - 'október' + - 'november' + - 'december' + DAYS_OF_THE_WEEK: + - 'hétfő' + - 'kedd' + - 'szerda' + - 'csütörtök' + - 'péntek' + - 'szombat' + - 'vasárnap' + CRON: + EVERY: minden + EVERY_HOUR: óránként + EVERY_MINUTE: percenként + EVERY_DAY_OF_WEEK: a hét minden napján + EVERY_DAY_OF_MONTH: a hónap minden napján + EVERY_MONTH: minden hónapban + TEXT_PERIOD: Minden + TEXT_MINS: ' perccel az óra elteltével' + ERROR1: A %s címke nem engedélyezett! + ERROR2: Hibás elemszám + ERROR3: A jquery_element-et a jqCron beállítsokban kell meghatározni + ERROR4: Ismeretlen kifejezés diff --git a/system/languages/id.yaml b/system/languages/id.yaml new file mode 100644 index 0000000..8107235 --- /dev/null +++ b/system/languages/id.yaml @@ -0,0 +1,147 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Error: Frontmatter tidak valid\n\nLokasi: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_PLURALS: + '/(quiz)$/i': '\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' + INFLECTOR_SINGULAR: + '/(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' + INFLECTOR_UNCOUNTABLE: + - 'Peralatan' + - 'Informasi ' + - 'Nasi' + - 'Uang' + - 'Jenis' + - 'Seri' + - 'Ikan' + - 'Domba' + INFLECTOR_IRREGULAR: + 'person': 'Orang-orang' + 'man': 'Pria' + 'child': 'Balita' + 'sex': 'Jenis Kelamin' + 'move': 'pindahkan' + INFLECTOR_ORDINALS: + 'default': 'ke' + 'first': 'pertama' + 'second': 'nd' + 'third': 'rd' + NICETIME: + NO_DATE_PROVIDED: Tidak ada tanggal yang disediakan + BAD_DATE: Format tanggal salah + AGO: yang lalu + FROM_NOW: dari sekarang + JUST_NOW: baru saja + SECOND: detik + MINUTE: menit + HOUR: jam + DAY: hari + WEEK: pekan + MONTH: bulan + YEAR: tahun + DECADE: dekade + SEC: detik + MIN: menit + HR: ' jam' + WK: minggu + MO: bulan + YR: tahun + DEC: desimal + SECOND_PLURAL: detik + MINUTE_PLURAL: menit + HOUR_PLURAL: jam + DAY_PLURAL: hari + WEEK_PLURAL: pekan + MONTH_PLURAL: bulan + YEAR_PLURAL: tahun + DECADE_PLURAL: dekade + SEC_PLURAL: detik + MIN_PLURAL: menit + HR_PLURAL: jam + WK_PLURAL: minggu + MO_PLURAL: bulan + YR_PLURAL: tahun + DEC_PLURAL: dekade + FORM: + VALIDATION_FAIL: 'Validasi gagal:' + INVALID_INPUT: 'Input tidak valid di' + MISSING_REQUIRED_FIELD: 'Data yang diperlukan belum terisi:' + XSS_ISSUES: "Isu berpotensial XSS terdeteksi dalam baris %s" + MONTHS_OF_THE_YEAR: + - 'Januari' + - 'Februari' + - 'Maret' + - 'April' + - 'Mei' + - 'Juni' + - 'Juli' + - 'Agustus' + - 'September' + - 'Oktober' + - 'November' + - 'Desember' + DAYS_OF_THE_WEEK: + - 'Senin' + - 'Selasa' + - 'Rabu' + - 'Kamis' + - 'Jum''at' + - 'Sabtu' + - 'Minggu' + YES: "Ya" + NO: "Tidak" + CRON: + EVERY: Setiap + EVERY_HOUR: Setiap jam + EVERY_MINUTE: Setiap menit + EVERY_DAY_OF_WEEK: Setiap hari selama seminggu + EVERY_DAY_OF_MONTH: Setiap hari dalam sebulan + EVERY_MONTH: setiap bulan + TEXT_PERIOD: Setiap + TEXT_MINS: 'dalam menit setelah jam yang lalu' + TEXT_TIME: ' pada :' + TEXT_DOW: ' pada ' + TEXT_MONTH: ' pada ' + TEXT_DOM: ' pada ' + ERROR1: Tag %s tidak didukung! + ERROR2: Jumlah elemen yang buruk + ERROR3: jquery_element harus diatur ke dalam pengaturan jqCron + ERROR4: Ekspresi tidak dikenal diff --git a/system/languages/is.yaml b/system/languages/is.yaml new file mode 100644 index 0000000..c6f8f5d --- /dev/null +++ b/system/languages/is.yaml @@ -0,0 +1,80 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\ntitill: %1$s\n---\n\n# Villa: Ógilt efni á forsíðu\n\nSlóð: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_UNCOUNTABLE: + - '' + - 'upplýsingar' + - '' + - '' + - '' + - '' + - '' + - '' + NICETIME: + NO_DATE_PROVIDED: Engin dagsetning gefin + BAD_DATE: Röng dagsetning + AGO: síðan + JUST_NOW: í þessu + SECOND: sekúndu + MINUTE: mínútu + HOUR: klukkustund + DAY: degi + WEEK: viku + MONTH: mánuði + YEAR: ári + DECADE: áratug + SEC: sek + MIN: mín + HR: klst + WK: vk + MO: mán + YR: ár + DEC: árat + SECOND_PLURAL: sekúndum + MINUTE_PLURAL: mínútum + HOUR_PLURAL: klukkustundum + DAY_PLURAL: dögum + WEEK_PLURAL: vikum + MONTH_PLURAL: mánuðum + YEAR_PLURAL: árum + DECADE_PLURAL: áratugum + SEC_PLURAL: sek + MIN_PLURAL: mín + HR_PLURAL: klst + WK_PLURAL: vik + MO_PLURAL: mán + YR_PLURAL: árum + DEC_PLURAL: árat + FORM: + VALIDATION_FAIL: 'Sannvottun mistókst:' + INVALID_INPUT: 'Ógilt inntak í' + MISSING_REQUIRED_FIELD: 'Vantar nauðsynlegan reit:' + MONTHS_OF_THE_YEAR: + - 'janúar' + - 'Febrúar' + - 'Mars' + - 'Apríl' + - 'Maí' + - 'Júní' + - 'Júlí' + - 'Ágúst' + - 'September' + - 'Október' + - 'Nóvember' + - 'Desember' + DAYS_OF_THE_WEEK: + - 'Mánudagur' + - 'Þriðjudagur' + - 'Miðvikudagur' + - 'Fimmtudagur' + - 'Föstudagur' + - 'Laugardagur' + - 'Sunnudagur' + CRON: + TEXT_TIME: ' á :' + TEXT_DOW: ' á ' + TEXT_MONTH: ' af ' + TEXT_DOM: ' á ' + ERROR1: Merkið %s er ekki stutt! + ERROR3: Það ætti að setja jquery_element inn í stillingar jqCron + ERROR4: Óþekkt segð diff --git a/system/languages/it.yaml b/system/languages/it.yaml new file mode 100644 index 0000000..f366eb6 --- /dev/null +++ b/system/languages/it.yaml @@ -0,0 +1,147 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---Titolo: %1$s---# Errore: Frontmatter non valido: '%2$s' * *%3$s * * ' '%4$s ' '" + INFLECTOR_PLURALS: + '/(quiz)$/i': '\1' + '/^(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' + INFLECTOR_SINGULAR: + '/(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' + INFLECTOR_UNCOUNTABLE: + - 'dotazione' + - 'informazione' + - 'riso' + - 'denaro' + - 'specie' + - 'serie' + - 'pesce' + - 'pecora' + INFLECTOR_IRREGULAR: + 'person': 'persone' + 'man': 'uomini' + 'child': 'bambino' + 'sex': 'sessi' + 'move': 'sposta' + INFLECTOR_ORDINALS: + 'default': '°' + 'first': '°' + 'second': 'o' + 'third': 'o' + NICETIME: + NO_DATE_PROVIDED: Nessuna data fornita + BAD_DATE: Data non valida + AGO: fa + FROM_NOW: da adesso + JUST_NOW: ora + SECOND: secondo + MINUTE: minuto + HOUR: ora + DAY: giorno + WEEK: settimana + MONTH: mese + YEAR: anno + DECADE: decennio + SEC: sec + MIN: min + HR: ora + WK: settimana + MO: mese + YR: anno + DEC: decennio + SECOND_PLURAL: secondi + MINUTE_PLURAL: minuti + HOUR_PLURAL: ore + DAY_PLURAL: giorni + WEEK_PLURAL: settimane + MONTH_PLURAL: mesi + YEAR_PLURAL: anni + DECADE_PLURAL: decadi + SEC_PLURAL: secondi + MIN_PLURAL: minuti + HR_PLURAL: ore + WK_PLURAL: settimane + MO_PLURAL: mesi + YR_PLURAL: anni + DEC_PLURAL: decenni + FORM: + VALIDATION_FAIL: 'Validazione fallita:' + INVALID_INPUT: 'Input non valido in' + MISSING_REQUIRED_FIELD: 'Campo richiesto mancante:' + XSS_ISSUES: "Rilevati potenziali problemi di XSS nel campo '%s'" + MONTHS_OF_THE_YEAR: + - 'Gennaio' + - 'Febbraio' + - 'Marzo' + - 'Aprile' + - 'Maggio' + - 'Giugno' + - 'Luglio' + - 'Agosto' + - 'Settembre' + - 'Ottobre' + - 'Novembre' + - 'Dicembre' + DAYS_OF_THE_WEEK: + - 'Lunedì' + - 'Martedì' + - 'Mercoledì' + - 'Giovedì' + - 'Venerdì' + - 'Sabato' + - 'Domenica' + YES: "Sì" + NO: "No" + CRON: + EVERY: ogni + EVERY_HOUR: ogni ora + EVERY_MINUTE: ogni minuto + EVERY_DAY_OF_WEEK: ogni giorno della settimana + EVERY_DAY_OF_MONTH: ogni giorno del mese + EVERY_MONTH: ogni mese + TEXT_PERIOD: Ogni + TEXT_MINS: ' a minuto(i) dall''inizio dell''ora' + TEXT_TIME: ' alle :' + TEXT_DOW: ' su ' + TEXT_MONTH: ' di ' + TEXT_DOM: ' di ' + ERROR1: Il tag %s non è supportato! + ERROR2: Numero di elementi non valido + ERROR3: Il jquery_element deve essere impostato nelle impostazioni di jqCron + ERROR4: Espressione non riconosciuta diff --git a/system/languages/ja.yaml b/system/languages/ja.yaml new file mode 100644 index 0000000..16c015c --- /dev/null +++ b/system/languages/ja.yaml @@ -0,0 +1,81 @@ +--- +GRAV: + INFLECTOR_UNCOUNTABLE: + - '' + - '情報' + - '' + - 'お金' + - '' + - '' + - '魚' + - 'ヒツジ' + INFLECTOR_IRREGULAR: + 'person': 'みんな' + 'man': '人' + 'child': '子供' + 'sex': '性別' + 'move': '移動' + INFLECTOR_ORDINALS: + 'first': '番目' + NICETIME: + NO_DATE_PROVIDED: 日付が設定されていません + BAD_DATE: 不正な日付 + AGO: 前 + SECOND: 秒 + MINUTE: 分 + HOUR: 時 + DAY: 日 + WEEK: 週 + MONTH: 月 + YEAR: 年 + DECADE: 10年 + SEC: 秒 + MIN: 分 + HR: 時 + WK: 週 + MO: 月 + YR: 年 + SECOND_PLURAL: 秒 + MINUTE_PLURAL: 分 + HOUR_PLURAL: 時 + DAY_PLURAL: 日 + WEEK_PLURAL: 週 + MONTH_PLURAL: 月 + YEAR_PLURAL: 年 + DECADE_PLURAL: 10年 + SEC_PLURAL: 秒 + MIN_PLURAL: 分 + HR_PLURAL: 時 + WK_PLURAL: 週 + MO_PLURAL: 月 + YR_PLURAL: 年 + DEC_PLURAL: 10年 + FORM: + VALIDATION_FAIL: 'バリデーション失敗 :' + INVALID_INPUT: '不正な入力:' + MISSING_REQUIRED_FIELD: '必須項目が入力されていません:' + MONTHS_OF_THE_YEAR: + - '1月' + - '2月' + - '3月' + - '4月' + - '5月' + - '6月' + - '7月' + - '8月' + - '9月' + - '10月' + - '11月' + - '12月' + DAYS_OF_THE_WEEK: + - '月' + - '火' + - '水' + - '木' + - '金' + - '土' + - '日' + CRON: + EVERY: 毎 + EVERY_MONTH: 毎月 + ERROR1: 共有タイプ %s はサポートされていません diff --git a/system/languages/ko.yaml b/system/languages/ko.yaml new file mode 100644 index 0000000..f7dca33 --- /dev/null +++ b/system/languages/ko.yaml @@ -0,0 +1,90 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# 오류: 무효의 Frontmatter\n\n경로: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_UNCOUNTABLE: + - '장비' + - '정보' + - '' + - '' + - '' + - '시리즈' + - '물고기' + - '' + INFLECTOR_IRREGULAR: + 'person': '사람들' + NICETIME: + NO_DATE_PROVIDED: 제공된 날짜가 없습니다 + BAD_DATE: 잘못된 날짜 + AGO: 전 + FROM_NOW: 후 + JUST_NOW: 방금 + SECOND: 초 + MINUTE: 분 + HOUR: 시간 + DAY: 일 + WEEK: 주 + MONTH: 개월 + YEAR: 년 + DECADE: 년간 + SEC: 초 + MIN: 분 + HR: 시간 + WK: 주 + MO: 개월 + YR: 년 + DEC: 년간 + SECOND_PLURAL: 초 + MINUTE_PLURAL: 분 + HOUR_PLURAL: 시간 + DAY_PLURAL: 일 + WEEK_PLURAL: 주 + MONTH_PLURAL: 개월 + YEAR_PLURAL: 년 + DECADE_PLURAL: 년간 + SEC_PLURAL: 초 + MIN_PLURAL: 분 + HR_PLURAL: 시간 + WK_PLURAL: 주 + MO_PLURAL: 개월 + YR_PLURAL: 년 + DEC_PLURAL: 년간 + FORM: + VALIDATION_FAIL: '유효성 검사 실패:' + INVALID_INPUT: '잘못된 입력' + MISSING_REQUIRED_FIELD: '누락 된 필수 필드:' + XSS_ISSUES: "'%s' 필드에서 잠재적인 XSS 문제가 감지되었습니다." + MONTHS_OF_THE_YEAR: + - '일월' + - '이월' + - '삼월' + - '사월' + - '오월' + - '유월' + - '칠월' + - '팔월' + - '구월' + - '시월' + - '십일월' + - '십이월' + DAYS_OF_THE_WEEK: + - '월요일' + - '화요일' + - '수요일' + - '목요일' + - '금요일' + - '토요일' + - '일요일' + YES: "네" + NO: "아니요" + CRON: + EVERY: 모두 + EVERY_HOUR: 매 시간 + EVERY_MINUTE: 매 분 + EVERY_DAY_OF_WEEK: 일주일간 매일 + EVERY_DAY_OF_MONTH: 일개월간 매일 + EVERY_MONTH: 매달 + TEXT_PERIOD: 모든 + ERROR1: '%s 태그는 지원되지 않습니다. ' + ERROR2: 잘못된 요소 수 + ERROR3: jquery_element는 jqCron 설정에서 설정할 수 있습니다. + ERROR4: 인식할 수 없는 표현 diff --git a/system/languages/lt.yaml b/system/languages/lt.yaml new file mode 100644 index 0000000..88914ed --- /dev/null +++ b/system/languages/lt.yaml @@ -0,0 +1,78 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Klaida: klaidinga įžanginė konfigūracija\n\nPath: `%2$s`\n\n**%3$s**\n\n```\n %4$s\n```" + INFLECTOR_UNCOUNTABLE: + - '' + - '' + - 'ryžiai' + - 'pinigai' + - 'prieskoniai' + - 'serijos' + - 'žuvis' + - 'avis' + INFLECTOR_IRREGULAR: + 'person': 'žmonės' + 'man': 'žmogus' + 'child': 'vaikai' + 'sex': 'lytys' + 'move': 'juda' + NICETIME: + NO_DATE_PROVIDED: Nenurodyta data + BAD_DATE: Neteisinga data + AGO: prieš + FROM_NOW: nuo dabar + SECOND: sekundė + MINUTE: minutė + HOUR: valanda + DAY: diena + WEEK: savaitė + MONTH: mėnuo + YEAR: metai + DECADE: dešimtmetis + SEC: sek. + MIN: min. + HR: val. + WK: sav. + MO: mėn. + YR: m. + DEC: dešimtmetis + SECOND_PLURAL: sekundės + MINUTE_PLURAL: minutės + HOUR_PLURAL: valandos + DAY_PLURAL: dienos + WEEK_PLURAL: savaitės + MONTH_PLURAL: mėnesiai + YEAR_PLURAL: metai + DECADE_PLURAL: dešimtmečiai + SEC_PLURAL: sek. + MIN_PLURAL: min. + HR_PLURAL: val. + WK_PLURAL: sav. + MO_PLURAL: mėn. + YR_PLURAL: m. + DEC_PLURAL: dešimtmečiai + FORM: + VALIDATION_FAIL: 'Patvirtinimas nepavyko:' + INVALID_INPUT: 'Neteisingai įvesta į' + MISSING_REQUIRED_FIELD: 'Būtina užpildyti laukelį:' + MONTHS_OF_THE_YEAR: + - 'Sausis' + - 'Vasaris' + - 'Kovas' + - 'Balandis' + - 'Gegužė' + - 'Birželis' + - 'Liepa' + - 'Rugpjūtis' + - 'Rugsėjis' + - 'Spalis' + - 'Lakpritis' + - 'Gruodis' + DAYS_OF_THE_WEEK: + - 'Pirmadienis' + - 'Antradienis' + - 'Trečiadienis' + - 'Ketvirtadienis' + - 'Penktadienis' + - 'Šeštadienis' + - 'Sekmadienis' diff --git a/system/languages/lv.yaml b/system/languages/lv.yaml new file mode 100644 index 0000000..b096c96 --- /dev/null +++ b/system/languages/lv.yaml @@ -0,0 +1,84 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\nNosaukums: %1$s\n---\n\n# Kļūda: Nederīgs Frontmatter\n\nCeļš: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_ORDINALS: + 'default': '.' + 'first': '.' + 'second': '.' + 'third': '.' + NICETIME: + NO_DATE_PROVIDED: Nav norādīts datums + BAD_DATE: Nederīgs datums + AGO: iepriekš + FROM_NOW: no šī brīža + JUST_NOW: tikko + SECOND: sekundes + MINUTE: minūte + HOUR: stunda + DAY: diena + WEEK: nedēļa + MONTH: mēnesis + YEAR: gads + DECADE: dekāde + SEC: s + MIN: m + HR: st + WK: ned + MO: mēn. + YR: g. + DEC: dec + SECOND_PLURAL: sekundes + MINUTE_PLURAL: minūtes + HOUR_PLURAL: stundas + DAY_PLURAL: dienas + WEEK_PLURAL: nedēļas + MONTH_PLURAL: mēneši + YEAR_PLURAL: gadi + DECADE_PLURAL: desmitgades + SEC_PLURAL: s + MIN_PLURAL: m + HR_PLURAL: st. + WK_PLURAL: ned. + MO_PLURAL: mēn. + YR_PLURAL: g. + DEC_PLURAL: d + FORM: + VALIDATION_FAIL: 'Validācija neizdevās:' + INVALID_INPUT: 'Nederīga ievade' + MISSING_REQUIRED_FIELD: 'Laukā trūkst datu' + XSS_ISSUES: "Atrastas iespējamas XSS problēmas laukā '%s'" + MONTHS_OF_THE_YEAR: + - 'Janvāris' + - 'Februāris' + - 'Marts' + - 'Aprīlis' + - 'Maijs' + - 'Jūnijs' + - 'Jūlijs' + - 'Augusts' + - 'Septembris' + - 'Oktobris' + - 'Novembris' + - 'Decembris' + DAYS_OF_THE_WEEK: + - 'Pirmdiena' + - 'Otrdiena' + - 'Trešdiena' + - 'Ceturtdiena' + - 'Piektdiena' + - 'Sestdiena' + - 'Svētdiena' + YES: "Jā" + NO: "Nē" + CRON: + EVERY: katru + EVERY_HOUR: katru stundu + EVERY_MINUTE: katru minūti + EVERY_DAY_OF_WEEK: katru nedēļas dienu + EVERY_DAY_OF_MONTH: katru mēneša dienu + EVERY_MONTH: katru mēnesi + TEXT_PERIOD: Katru + ERROR1: Marķieris %s nav atbalstīts! + ERROR2: Nederīgs elementu skaits + ERROR3: jquery_element nevajadzētu definēt jqCron iestatījumos + ERROR4: Neatpazīta izteiksme diff --git a/system/languages/mn.yaml b/system/languages/mn.yaml new file mode 100644 index 0000000..73cda0e --- /dev/null +++ b/system/languages/mn.yaml @@ -0,0 +1,147 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\nГарчиг: %1$s\n---\n\n# Алдаа: Буруу Формат\n\nЗам: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_PLURALS: + '/(quiz)$/i': '\1зүүд' + '/^(ox)$/i': '\1ууд' + '/([m|l])ouse$/i': '\1ууд' + '/(matr|vert|ind)ix|ex$/i': '\1иксүүд' + '/(x|ch|ss|sh)$/i': '\1үүд' + '/([^aeiouy]|qu)ies$/i': '\1үүд' + '/([^aeiouy]|qu)y$/i': '\1үүд' + '/(hive)$/i': '\1үүд' + '/(?:([^f])fe|([lr])f)$/i': '\1\2үүд' + '/sis$/i': 'үүд' + '/([ti])um$/i': '\1үүд' + '/(buffal|tomat)o$/i': '\1үүд' + '/(bu)s$/i': '\1үүд' + '/(alias|status)/i': '\1үүд' + '/(octop|vir)us$/i': '\1үүд' + '/(ax|test)is$/i': '\1үүд' + '/s$/i': 'үүд' + '/$/': 'үүд' + INFLECTOR_SINGULAR: + '/(quiz)zes$/i': '\1' + '/(matr)ices$/i': '\1икс' + '/(vert|ind)ices$/i': '\1икс' + '/^(ox)en/i': '\1' + '/(alias|status)es$/i': '\1' + '/([octop|vir])i$/i': '\1' + '/(cris|ax|test)es$/i': '\1' + '/(shoe)s$/i': '\1' + '/(o)es$/i': '\1' + '/(bus)es$/i': '\1' + '/([m|l])ice$/i': '\1' + '/(x|ch|ss|sh)es$/i': '\1' + '/(m)ovies$/i': '\1' + '/(s)eries$/i': '\1' + '/([^aeiouy]|qu)ies$/i': '\1үүд' + '/([lr])ves$/i': '\1' + '/(tive)s$/i': '\1' + '/(hive)s$/i': '\1' + '/([^f])ves$/i': '\1' + '/(^analy)ses$/i': '\1' + '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i': '\1\2үүд' + '/([ti])a$/i': '\1' + '/(n)ews$/i': '\1' + INFLECTOR_UNCOUNTABLE: + - 'тоног төхөөрөмж' + - 'Мэдээлэл' + - 'будаа' + - 'мөнгө' + - 'төрөл зүйл' + - 'цуврал' + - 'загас' + - 'хонь' + INFLECTOR_IRREGULAR: + 'person': 'хүмүүс' + 'man': 'эрчүүд' + 'child': 'хүүхэд' + 'sex': 'хүйс' + 'move': 'хөдөлгөөн' + INFLECTOR_ORDINALS: + 'default': 'th' + 'first': 'st' + 'second': 'nd' + 'third': 'rd' + NICETIME: + NO_DATE_PROVIDED: Огноо алга + BAD_DATE: Буруу огноо + AGO: өмнө + FROM_NOW: одооноос + JUST_NOW: дөнгөж сая + SECOND: секунд + MINUTE: минут + HOUR: цаг + DAY: өдөр + WEEK: долоо хоног + MONTH: сар + YEAR: он + DECADE: арван жил + SEC: сек + MIN: мин + HR: цаг + WK: д.х. + MO: сар + YR: он + DEC: арван жил + SECOND_PLURAL: секунд + MINUTE_PLURAL: минут + HOUR_PLURAL: цаг + DAY_PLURAL: өдрүүд + WEEK_PLURAL: долоо хоногууд + MONTH_PLURAL: сарууд + YEAR_PLURAL: онууд + DECADE_PLURAL: арван жилүүд + SEC_PLURAL: сек.-үүд + MIN_PLURAL: мин.-ууд + HR_PLURAL: цагууд + WK_PLURAL: д.х.-ууд + MO_PLURAL: сарууд + YR_PLURAL: жилүүд + DEC_PLURAL: арван жилүүд + FORM: + VALIDATION_FAIL: 'Баталгаажуулалт амжилтгүй боллоо:' + INVALID_INPUT: 'Буруу өгөгдөл дараахид' + MISSING_REQUIRED_FIELD: 'Шаардлагатай талбар дутуу байна:' + XSS_ISSUES: "'%s' талбарт XSS -ийн болзошгүй асуудлууд илэрсэн" + MONTHS_OF_THE_YEAR: + - '1-р сар' + - '2-р сар' + - '3-р сар' + - '4-р сар' + - '5 сар' + - '6 сар' + - '7 сар' + - '8 сар' + - '9 сар' + - '10 сар' + - '11 сар' + - '12 сар' + DAYS_OF_THE_WEEK: + - 'Даваа гараг' + - 'Мягмар гараг' + - 'Лхагва гараг' + - 'Пүрэв гараг' + - 'Баасан гараг' + - 'Бямба гараг' + - 'Ням гараг' + YES: "Тийм" + NO: "Үгүй" + CRON: + EVERY: бүрийн + EVERY_HOUR: цаг бүрийн + EVERY_MINUTE: минут бүрийн + EVERY_DAY_OF_WEEK: долоо хоногийн өдөр болгонд + EVERY_DAY_OF_MONTH: сарын өдөр болгонд + EVERY_MONTH: сар болгон + TEXT_PERIOD: Бүрийн + TEXT_MINS: ' энэ сүүлийн цагийн минутад' + TEXT_TIME: ' : -д' + TEXT_DOW: ' -д' + TEXT_MONTH: ' -ын' + TEXT_DOM: ' -т' + ERROR1: '%s -н утга нь дэмжигддэггүй!' + ERROR2: Элементүүдийн тоо хэмжээ буруу + ERROR3: jquery_element нь jqCron тохиргоонд хийгдсэн байх ёстой + ERROR4: Танигдаагүй илэрхийлэл diff --git a/system/languages/my.yaml b/system/languages/my.yaml new file mode 100644 index 0000000..3236cd1 --- /dev/null +++ b/system/languages/my.yaml @@ -0,0 +1,147 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\nခေါင်းစဥ်: %1$s\n---\n\n# အမှား - Frontmatter မမှန်ကန်ပါ\n\nလမ်းကြောင်း `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_PLURALS: + '/(quiz)$/i': '\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' + INFLECTOR_SINGULAR: + '/(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' + INFLECTOR_UNCOUNTABLE: + - 'ကိရိယာ' + - 'အချက်အလက်' + - 'ဆန်' + - 'ငွေ' + - 'မျိုးစိတ်' + - 'အတွဲများ' + - 'ငါး' + - 'သိုးများ' + INFLECTOR_IRREGULAR: + 'person': 'လူ' + 'man': 'ယောက်ျား' + 'child': 'ကလေးများ' + 'sex': 'လိင်' + 'move': 'ရွှေ့ခြင်း' + INFLECTOR_ORDINALS: + 'default': 'th' + 'first': 'st' + 'second': 'nd' + 'third': 'rd' + NICETIME: + NO_DATE_PROVIDED: နေ့စွဲ မသတ်မှတ်ထား + BAD_DATE: ရက်စွဲမမှန်ပါ + AGO: လွန်ခဲ့တဲ့ + FROM_NOW: ယခုမှ + JUST_NOW: အခုပဲ + SECOND: ဒုတိယ + MINUTE: မိနစ် + HOUR: နာရီ + DAY: နေ့ + WEEK: တစ်ပတ် + MONTH: လ + YEAR: နှစ် + DECADE: ဆယ်စုနှစ် + SEC: စက္ကန့် + MIN: မိနစ် + HR: နာရီ + WK: တစ်ပတ် + MO: လ + YR: နှစ် + DEC: ဒီဇင်ဘာ + SECOND_PLURAL: စက္ကန့် + MINUTE_PLURAL: မိနစ် + HOUR_PLURAL: နာရီ + DAY_PLURAL: နေ့ + WEEK_PLURAL: ရက်သတ္တပတ် + MONTH_PLURAL: လ + YEAR_PLURAL: နှစ် + DECADE_PLURAL: ဆယ်စုနှစ်များစွ + SEC_PLURAL: စက္ကန့် + MIN_PLURAL: မိနစ် + HR_PLURAL: နာရီ + WK_PLURAL: အပတ် + MO_PLURAL: လ + YR_PLURAL: နှစ် + DEC_PLURAL: ဆယ်စုနှစ် + FORM: + VALIDATION_FAIL: ' အတည်ပြုခြင်းမအောင်မြင်ပါ: ' + INVALID_INPUT: 'ထည့်သွင်းမှုမမှန်ပါ' + MISSING_REQUIRED_FIELD: 'လိုအပ်သောအကွက်ပျောက်နေသည်' + XSS_ISSUES: "XSS ပြဿနာ ဖြစ်နိုင်ချေ ကို '%s' အကွက်တွင် တွေ့" + MONTHS_OF_THE_YEAR: + - 'ဇန်နဝါရီ' + - 'ဖေဖော်ဝါရီ' + - 'မတ်' + - 'ဧပြီ' + - 'မေ' + - 'ဇွန်' + - 'ဇူလိုင်' + - 'သြဂုတ်' + - 'စက်တင်ဘာ' + - 'အောက်တိုဘာ' + - 'နိုဝင်ဘာ' + - 'ဒီဇင်ဘာ' + DAYS_OF_THE_WEEK: + - 'တနင်္လာ' + - ' အင်္ဂါ' + - 'ဗုဒ္ဓဟူး' + - 'ကြာသပတေး' + - 'သောကြာ' + - 'စနေ' + - 'တနင်္ဂနွေ' + YES: "လုပ်" + NO: "မလုပ်" + CRON: + EVERY: အမြဲတမ်း + EVERY_HOUR: နာရီတိုင်း + EVERY_MINUTE: မိနစ်တိုင်း + EVERY_DAY_OF_WEEK: တစ်ပတ်လုံး နေ့တိုင်း + EVERY_DAY_OF_MONTH: တစ်လလုံး နေ့တိုင်း + EVERY_MONTH: လစဉ်လတိုင်း + TEXT_PERIOD: တိုင်း + TEXT_MINS: 'နာရီ ကျော်ပြီး မိနစ် တွင်' + TEXT_TIME: ' : တွင် ' + TEXT_DOW: ' ပေါ်တွင် ' + TEXT_MONTH: '၏ ' + TEXT_DOM: ' တွင် ' + ERROR1: ဤ %s တက် ကိုပံ့ပိုးမထားပါ။ + ERROR2: လိုအပ်သောထည့်သွင်း နာပတ် အမှားဖြစ်နေသည် + ERROR3: jquery_element ကို jqCron ဆက်တင် တွင်ထားရမည် + ERROR4: အသိအမှတ်မပြုသော အသုံးအနှုန်း diff --git a/system/languages/nb.yaml b/system/languages/nb.yaml new file mode 100644 index 0000000..8033e79 --- /dev/null +++ b/system/languages/nb.yaml @@ -0,0 +1,4 @@ +--- +GRAV: + MONTHS_OF_THE_YEAR: ['januar', 'februar', 'mars', 'april', 'mai', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'desember'] + DAYS_OF_THE_WEEK: ['mandag', 'tirsdag', 'onsdag', 'torsdag', 'fredag', 'lørdag', 'søndag'] diff --git a/system/languages/nl.yaml b/system/languages/nl.yaml new file mode 100644 index 0000000..0ad6c96 --- /dev/null +++ b/system/languages/nl.yaml @@ -0,0 +1,147 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\ntitel: %1$s\n---\n\n# Fout: ongeldige frontmatter\n\nPad: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_PLURALS: + '/(quiz)$/i': '\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' + INFLECTOR_SINGULAR: + '/(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' + INFLECTOR_UNCOUNTABLE: + - 'uitrusting' + - 'informatie' + - 'rijst' + - 'geld' + - 'soorten' + - 'reeks' + - 'vis' + - 'schaap' + INFLECTOR_IRREGULAR: + 'person': 'personen' + 'man': 'mensen' + 'child': 'kinderen' + 'sex': 'geslacht' + 'move': 'verplaatsen' + INFLECTOR_ORDINALS: + 'default': 'th' + 'first': 'st' + 'second': 'nd' + 'third': 'rd' + NICETIME: + NO_DATE_PROVIDED: geen datum opgegeven + BAD_DATE: Datumformaat onjuist + AGO: geleden + FROM_NOW: vanaf nu + JUST_NOW: zojuist + SECOND: seconde + MINUTE: minuut + HOUR: uur + DAY: dag + WEEK: week + MONTH: maand + YEAR: jaar + DECADE: decennium + SEC: s + MIN: min + HR: u + WK: week + MO: ma + YR: j + DEC: decennia + SECOND_PLURAL: seconden + MINUTE_PLURAL: minuten + HOUR_PLURAL: uren + DAY_PLURAL: dagen + WEEK_PLURAL: weken + MONTH_PLURAL: maanden + YEAR_PLURAL: jaren + DECADE_PLURAL: decennia + SEC_PLURAL: seconden + MIN_PLURAL: minuten + HR_PLURAL: uren + WK_PLURAL: weken + MO_PLURAL: maanden + YR_PLURAL: jaren + DEC_PLURAL: decennia + FORM: + VALIDATION_FAIL: 'Validatie mislukt:' + INVALID_INPUT: 'Ongeldige invoer in' + MISSING_REQUIRED_FIELD: 'Ontbrekend verplicht veld:' + XSS_ISSUES: "Mogelijke XSS-problemen ontdekt in '%s' veld" + MONTHS_OF_THE_YEAR: + - 'Januari' + - 'Februari' + - 'Maart' + - 'April' + - 'Mei' + - 'Juni' + - 'Juli' + - 'Augustus' + - 'September' + - 'Oktober' + - 'November' + - 'December' + DAYS_OF_THE_WEEK: + - 'Maandag' + - 'Dinsdag' + - 'Woensdag' + - 'Donderdag' + - 'Vrijdag' + - 'Zaterdag' + - 'Zondag' + YES: "Ja" + NO: "Nee" + CRON: + EVERY: elke + EVERY_HOUR: elk uur + EVERY_MINUTE: elke minuut + EVERY_DAY_OF_WEEK: elke dag van de week + EVERY_DAY_OF_MONTH: elke dag van de maand + EVERY_MONTH: elke maand + TEXT_PERIOD: Elke + TEXT_MINS: ' minuten te laat' + TEXT_TIME: ' op :' + TEXT_DOW: ' op ' + TEXT_MONTH: ' van ' + TEXT_DOM: ' op ' + ERROR1: De tag %s wordt niet ondersteund! + ERROR2: Slecht aantal elementen + ERROR3: Het jquery_element moet ingesteld worden in de jqCron instellingen + ERROR4: Onbekende expressie diff --git a/system/languages/no.yaml b/system/languages/no.yaml new file mode 100644 index 0000000..2a93e6e --- /dev/null +++ b/system/languages/no.yaml @@ -0,0 +1,82 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\nTittel: %1$s\n---\n\n# Feilmelding: Ugyldig Frontmatter\n\nSti: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_UNCOUNTABLE: + - 'utstyr' + - 'informasjon' + - 'ris' + - 'penger' + - 'arter' + - 'serier' + - 'fisk' + - 'sau' + INFLECTOR_IRREGULAR: + 'person': 'folk' + 'man': 'menn' + 'child': 'barn' + 'sex': 'kjønn' + 'move': 'trekk' + NICETIME: + NO_DATE_PROVIDED: Ingen dato gitt + BAD_DATE: Ugyldig dato + AGO: siden + FROM_NOW: fra nå + JUST_NOW: akkurat nå + SECOND: sekund + MINUTE: minutt + HOUR: time + DAY: dag + WEEK: uke + MONTH: måned + YEAR: år + DECADE: tiår + SEC: sek + HR: t + WK: uke + MO: må + YR: år + DEC: tiår + SECOND_PLURAL: sekunder + MINUTE_PLURAL: minutter + HOUR_PLURAL: timer + DAY_PLURAL: dager + WEEK_PLURAL: uker + MONTH_PLURAL: måneder + YEAR_PLURAL: år + DECADE_PLURAL: tiår + SEC_PLURAL: sek + MIN_PLURAL: min + HR_PLURAL: timer + WK_PLURAL: uker + MO_PLURAL: md + YR_PLURAL: år + DEC_PLURAL: årtier + FORM: + VALIDATION_FAIL: 'Godkjenning mislyktes:' + INVALID_INPUT: 'Ugyldig innhold i' + MISSING_REQUIRED_FIELD: 'Mangler påkrevd felt:' + MONTHS_OF_THE_YEAR: + - 'januar' + - 'februar' + - 'mars' + - 'april' + - 'mai' + - 'juni' + - 'juli' + - 'august' + - 'september' + - 'oktober' + - 'november' + - 'desember' + DAYS_OF_THE_WEEK: + - 'mandag' + - 'tirsdag' + - 'onsdag' + - 'torsdag' + - 'fredag' + - 'lørdag' + - 'søndag' + CRON: + EVERY: hver + EVERY_HOUR: hver time + EVERY_MINUTE: hvert minutt diff --git a/system/languages/pl.yaml b/system/languages/pl.yaml new file mode 100644 index 0000000..360e41e --- /dev/null +++ b/system/languages/pl.yaml @@ -0,0 +1,100 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Error: Nieprawidłowy Frontmatter\n\nPath: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_SINGULAR: + '/(alias|status)es$/i': '\1' + INFLECTOR_UNCOUNTABLE: + - 'wyposażenie' + - 'informacja' + - '' + - 'pieniądze' + - '' + - '' + - 'ryba' + - 'owca' + INFLECTOR_IRREGULAR: + 'person': 'człowiek' + 'man': 'mężczyźni' + 'child': 'dzieci' + 'sex': 'płci' + INFLECTOR_ORDINALS: + 'first': 'pierwszy' + 'second': 'drugi' + 'third': 'trzeci' + NICETIME: + NO_DATE_PROVIDED: Nie podano daty + BAD_DATE: Zła data + AGO: temu + FROM_NOW: od teraz + JUST_NOW: właśnie teraz + SECOND: sekunda + MINUTE: minuta + HOUR: godzina + DAY: dzień + WEEK: tydzień + MONTH: miesiąc + YEAR: rok + DECADE: dekada + SEC: sek + MIN: minuta + HR: godz + WK: tydz + MO: m-c + YR: rok + DEC: dekada + SECOND_PLURAL: sekund + MINUTE_PLURAL: minut + HOUR_PLURAL: godzin + DAY_PLURAL: dni + WEEK_PLURAL: tygodnie + MONTH_PLURAL: miesięcy + YEAR_PLURAL: lat + DECADE_PLURAL: dekad + SEC_PLURAL: sek + MIN_PLURAL: min + HR_PLURAL: godz + WK_PLURAL: tyg + MO_PLURAL: m-ce + YR_PLURAL: lat + DEC_PLURAL: dekad + FORM: + VALIDATION_FAIL: 'Weryfikacja nie powiodła się:' + INVALID_INPUT: 'Nieprawidłowe dane wejściowe' + MISSING_REQUIRED_FIELD: 'Opuszczono wymagane pole:' + XSS_ISSUES: "Potencjalne problemy XSS wykryte w polu '%s'" + MONTHS_OF_THE_YEAR: + - 'Styczeń' + - 'Luty' + - 'Marzec' + - 'Kwiecień' + - 'Maj' + - 'Czerwiec' + - 'Lipiec' + - 'Sierpień' + - 'Wrzesień' + - 'Październik' + - 'Listopad' + - 'Grudzień' + DAYS_OF_THE_WEEK: + - 'Poniedziałek' + - 'Wtorek' + - 'Środa' + - 'Czwartek' + - 'Piątek' + - 'Sobota' + - 'Niedziela' + YES: "Tak" + NO: "Nie" + CRON: + EVERY: każdy + EVERY_HOUR: każdą godzinę + EVERY_MINUTE: każdą minutę + EVERY_DAY_OF_WEEK: każdego dnia tygodnia + EVERY_DAY_OF_MONTH: każdego dnia miesiące + EVERY_MONTH: każdego miesiąca + TEXT_PERIOD: Każdego + TEXT_MINS: 'o minut po godzinie' + TEXT_TIME: 'o :' + ERROR1: Znacznik %s nie jest wspierany! + ERROR2: Nieprawidłowa liczba elementów + ERROR4: Wyrażenie nierozpoznane diff --git a/system/languages/pt.yaml b/system/languages/pt.yaml new file mode 100644 index 0000000..c2442f3 --- /dev/null +++ b/system/languages/pt.yaml @@ -0,0 +1,147 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Erro: Frontmatter Inválido\n\nLocalização: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_PLURALS: + '/(quiz)$/i': '\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' + INFLECTOR_SINGULAR: + '/(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' + INFLECTOR_UNCOUNTABLE: + - 'equipamento' + - 'informação' + - 'arroz' + - 'dinheiro' + - 'espécie' + - 'série' + - 'peixe' + - 'ovelha' + INFLECTOR_IRREGULAR: + 'person': 'pessoas' + 'man': 'homens' + 'child': 'crianças' + 'sex': 'sexos' + 'move': 'movimentos' + INFLECTOR_ORDINALS: + 'default': 'º' + 'first': 'º' + 'second': 'º' + 'third': 'º' + NICETIME: + NO_DATE_PROVIDED: Nenhuma data fornecida + BAD_DATE: Data inválida + AGO: há + FROM_NOW: a partir de agora + JUST_NOW: mesmo agora + SECOND: segundo + MINUTE: minuto + HOUR: hora + DAY: dia + WEEK: semana + MONTH: mês + YEAR: ano + DECADE: década + SEC: seg + MIN: min + HR: hora + WK: semana + MO: mês + YR: ano + DEC: década + SECOND_PLURAL: segundos + MINUTE_PLURAL: minutos + HOUR_PLURAL: horas + DAY_PLURAL: dias + WEEK_PLURAL: semanas + MONTH_PLURAL: meses + YEAR_PLURAL: anos + DECADE_PLURAL: décadas + SEC_PLURAL: segs + MIN_PLURAL: mins + HR_PLURAL: hrs + WK_PLURAL: sems + MO_PLURAL: meses + YR_PLURAL: anos + DEC_PLURAL: décadas + FORM: + VALIDATION_FAIL: 'Falha na validação:' + INVALID_INPUT: 'Dados inseridos são inválidos em' + MISSING_REQUIRED_FIELD: 'Campo obrigatório em falta:' + XSS_ISSUES: "Potenciais problemas de XSS detectados no campo '%s'" + MONTHS_OF_THE_YEAR: + - 'Janeiro' + - 'Fevereiro' + - 'Março' + - 'Abril' + - 'Maio' + - 'Junho' + - 'Julho' + - 'Agosto' + - 'Setembro' + - 'Outubro' + - 'Novembro' + - 'Dezembro' + DAYS_OF_THE_WEEK: + - 'Segunda-feira' + - 'Terça-feira' + - 'Quarta-feira' + - 'Quinta-feira' + - 'Sexta-feira' + - 'Sábado' + - 'Domingo' + YES: "Sim" + NO: "Não" + CRON: + EVERY: cada + EVERY_HOUR: cada hora + EVERY_MINUTE: cada minuto + EVERY_DAY_OF_WEEK: todos os dias da semana + EVERY_DAY_OF_MONTH: todos os dias do mês + EVERY_MONTH: todos os meses + TEXT_PERIOD: Cada + TEXT_MINS: ' em minuto(s) após a hora' + TEXT_TIME: ' em :' + TEXT_DOW: ' em ' + TEXT_MONTH: ' de ' + TEXT_DOM: ' em ' + ERROR1: A tag %s não é suportada! + ERROR2: Número de elementos inválido + ERROR3: O jquery_element deve ser definido nas configurações do jqCron + ERROR4: Expressão não reconhecida diff --git a/system/languages/ro.yaml b/system/languages/ro.yaml new file mode 100644 index 0000000..dc22b20 --- /dev/null +++ b/system/languages/ro.yaml @@ -0,0 +1,96 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\nTitlu: %1$s\n---\n# Eroare: Frontmatter este invalid\n\nCalea: `%2$s`\n\n**%3$s**\n\n```\n%4$s" + INFLECTOR_UNCOUNTABLE: + - 'echipament' + - 'informaţie' + - 'orez' + - 'bani' + - 'specii' + - 'serii' + - 'peşte' + - 'oaie' + INFLECTOR_IRREGULAR: + 'person': 'persoane' + 'man': 'bărbați' + 'child': 'copii' + 'sex': 'sexe' + 'move': 'mutări' + NICETIME: + NO_DATE_PROVIDED: Nu există o dată prevăzută + BAD_DATE: Dată incorectă + AGO: în urmă + FROM_NOW: de acum + JUST_NOW: chiar acum + SECOND: secundă + MINUTE: minut + HOUR: oră + DAY: zi + WEEK: săptămână + MONTH: lună + YEAR: an + DECADE: decadă + SEC: secunde + MIN: minute + HR: oră + WK: săpt + MO: lună + YR: an + DEC: decadă + SECOND_PLURAL: secunde + MINUTE_PLURAL: minute + HOUR_PLURAL: ore + DAY_PLURAL: zile + WEEK_PLURAL: săptămâni + MONTH_PLURAL: luni + YEAR_PLURAL: ani + DECADE_PLURAL: decade + SEC_PLURAL: sec + MIN_PLURAL: min + HR_PLURAL: ore + WK_PLURAL: săpt + MO_PLURAL: luni + YR_PLURAL: ani + DEC_PLURAL: decenii + FORM: + VALIDATION_FAIL: 'Validare nereușită' + INVALID_INPUT: 'Date incorecte în' + MISSING_REQUIRED_FIELD: 'Câmp obligatoriu lipsă:' + MONTHS_OF_THE_YEAR: + - 'Ianuarie' + - 'Februarie' + - 'Martie' + - 'Aprilie' + - 'Mai' + - 'Iunie' + - 'Iulie' + - 'August' + - 'Septembrie' + - 'Octombrie' + - 'Noiembrie' + - 'Decembrie' + DAYS_OF_THE_WEEK: + - 'Luni' + - 'Marți' + - 'Miercuri' + - 'Joi' + - 'Vineri' + - 'Sâmbătă' + - 'Duminică' + CRON: + EVERY: la fiecare + EVERY_HOUR: la fiecare oră + EVERY_MINUTE: la fiecare minut + EVERY_DAY_OF_WEEK: fiecare zi a săptămânii + EVERY_DAY_OF_MONTH: fiecare zi a lunii + EVERY_MONTH: fiecare lună + TEXT_PERIOD: Fiecare + TEXT_MINS: ' la minut(e) ale fiecărei ore' + TEXT_TIME: ' la :' + TEXT_DOW: ' pe ' + TEXT_MONTH: 'al(e) ' + TEXT_DOM: ' pe ' + ERROR1: Eticheta %s nu este acceptată! + ERROR2: Număr nevalid de elemente + ERROR3: jquery_element ar trebui setat în opțiunile jqCron + ERROR4: Expresie necunoscută diff --git a/system/languages/ru.yaml b/system/languages/ru.yaml new file mode 100644 index 0000000..4829005 --- /dev/null +++ b/system/languages/ru.yaml @@ -0,0 +1,114 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Ошибка: недопустимое содержимое Frontmatter\n\nПуть: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_SINGULAR: + '/([octop|vir])i$/i': '\1us' + '/(cris|ax|test)es$/i': '\1is' + '/(shoe)s$/i': '\1' + '/([lr])ves$/i': '\1f' + '/(tive)s$/i': "\\1\n" + '/(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' + INFLECTOR_UNCOUNTABLE: + - 'экипировка' + - 'информация' + - 'рис' + - 'деньги' + - 'виды' + - 'серии' + - 'рыба' + - 'овца' + INFLECTOR_IRREGULAR: + 'person': 'люди' + 'man': 'человек' + 'child': 'дети' + 'sex': 'пол' + 'move': 'движется' + INFLECTOR_ORDINALS: + 'default': 'й' + 'first': 'й' + 'second': 'й' + 'third': 'й' + NICETIME: + NO_DATE_PROVIDED: Дата не указана + BAD_DATE: Неверная дата + AGO: назад + FROM_NOW: теперь + JUST_NOW: только что + SECOND: секунда + MINUTE: минута + HOUR: час + DAY: день + WEEK: неделя + MONTH: месяц + YEAR: год + DECADE: десятилетие + SEC: сек + MIN: мин + HR: ч + WK: нед + MO: мес + YR: г + DEC: дстлт + SECOND_PLURAL: сек + MINUTE_PLURAL: мин + HOUR_PLURAL: ч + DAY_PLURAL: д + WEEK_PLURAL: нед + MONTH_PLURAL: мес + YEAR_PLURAL: г + DECADE_PLURAL: дстлт + SEC_PLURAL: сек + MIN_PLURAL: мин + HR_PLURAL: ч + WK_PLURAL: нед + MO_PLURAL: мес + YR_PLURAL: г + DEC_PLURAL: дстлт + FORM: + VALIDATION_FAIL: 'Проверка не удалась:' + INVALID_INPUT: 'Неверный ввод в' + MISSING_REQUIRED_FIELD: 'Отсутствует необходимое поле:' + XSS_ISSUES: "Обнаружены потенциальные XSS проблемы в поле '%s'" + MONTHS_OF_THE_YEAR: + - 'январь' + - 'февраль' + - 'март' + - 'апрель' + - 'май' + - 'июнь' + - 'июль' + - 'август' + - 'сентябрь' + - 'октябрь' + - 'ноябрь' + - 'декабрь' + DAYS_OF_THE_WEEK: + - 'понедельник' + - 'вторник' + - 'среда' + - 'четверг' + - 'пятница' + - 'суббота' + - 'воскресенье' + YES: "Да" + NO: "Нет" + CRON: + EVERY: раз в + EVERY_HOUR: раз в час + EVERY_MINUTE: раз в минуту + EVERY_DAY_OF_WEEK: каждый день недели + EVERY_DAY_OF_MONTH: каждый день недели + EVERY_MONTH: раз в месяц + TEXT_PERIOD: Каждый + TEXT_MINS: ' в минуте(ах) за час' + TEXT_TIME: ' в :' + TEXT_DOW: ' на ' + TEXT_MONTH: ' из ' + TEXT_DOM: ' на ' + ERROR1: Тег %s не поддерживается! + ERROR2: Неверное количество элементов + ERROR3: jquery_element должен быть установлен в настройки jqCron + ERROR4: Выражение не распознано diff --git a/system/languages/si.yaml b/system/languages/si.yaml new file mode 100644 index 0000000..7a895da --- /dev/null +++ b/system/languages/si.yaml @@ -0,0 +1,120 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\nමාතෘකාව: %1$s\n---\n\n# දෝෂය: වලංගු නොවන ඉදිරිපස\n\nමාර්ගය: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_PLURALS: + '/([m|l])ouse$/i': '\1අයිස්' + '/(matr|vert|ind)ix|ex$/i': '\1අයිස්' + '/(?:([^f])fe|([lr])f)$/i': '\1\2වෙස්' + '/([ti])um$/i': '\1අ' + '/(buffal|tomat)o$/i': '\1ඕඑස්' + '/(bu)s$/i': '\1සෙස්' + INFLECTOR_SINGULAR: + '/(quiz)zes$/i': '\1' + '/^(ox)en/i': '\1' + '/(alias|status)es$/i': '\1' + '/([octop|vir])i$/i': '\1 අප' + '/(cris|ax|test)es$/i': '\1 වේ' + '/(o)es$/i': '\1' + '/(bus)es$/i': '\1' + '/([m|l])ice$/i': '\1 භාවිතා කරන්න' + '/(x|ch|ss|sh)es$/i': '\1' + '/(m)ovies$/i': '\1ඕවී' + '/(s)eries$/i': '\1මාලා' + '/(^analy)ses$/i': '\1සිස්' + '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i': '\1\2සිස්' + '/([ti])a$/i': '\1ම්' + INFLECTOR_UNCOUNTABLE: + - 'උපකරණ' + - 'විස්තර' + - 'සහල්' + - 'මුදල' + - 'විශේෂ' + - 'මාලාවක්' + - 'මාළු' + - 'බැටළුවන්' + INFLECTOR_IRREGULAR: + 'person': 'මහජන' + 'man': 'මිනිසුන්' + 'child': 'දරුවන්' + 'sex': 'ලිංගිකත්වය' + 'move': 'චලනය කරයි' + INFLECTOR_ORDINALS: + 'first': 'ශාන්ත' + NICETIME: + NO_DATE_PROVIDED: දිනයක් සපයා නැත + BAD_DATE: නරක දිනය + AGO: පෙර + FROM_NOW: මෙතැන් සිට + JUST_NOW: මේ දැන් + SECOND: දෙවැනි + MINUTE: මිනිත්තුව + HOUR: පැය + DAY: දින + WEEK: සතිය + MONTH: මස + YEAR: වර්ෂය + DECADE: දශකය + SEC: තත්පර + MIN: මිනි + HR: පැය + YR: වසර + DEC: දෙසැ + SECOND_PLURAL: තත්පර + MINUTE_PLURAL: මිනිත්තු + HOUR_PLURAL: පැය + DAY_PLURAL: දින + WEEK_PLURAL: සති + MONTH_PLURAL: මාස + YEAR_PLURAL: වසර + DECADE_PLURAL: දශක + SEC_PLURAL: තත්පර + MIN_PLURAL: මිනිත්තු + HR_PLURAL: පැය + WK_PLURAL: සති + YR_PLURAL: වසර + DEC_PLURAL: දෙසැ + FORM: + VALIDATION_FAIL: 'වලංගු කිරීම අසාර්ථක විය:' + INVALID_INPUT: 'වලංගු නොවන ආදානය' + MISSING_REQUIRED_FIELD: 'අවශ්‍ය ක්ෂේත්‍රය අස්ථානගත වී ඇත:' + XSS_ISSUES: "විභව XSS ගැටළු '%s' ක්ෂේත්‍රයේ අනාවරණය විය" + MONTHS_OF_THE_YEAR: + - 'ජනවාරි' + - 'පෙබරවාරි' + - 'මාර්තු' + - 'අප්රේල්' + - 'මැයි' + - 'ජූනි' + - 'ජුලි' + - 'අගෝස්තු' + - 'සැප්තැම්බර්' + - 'ඔක්තෝම්බර්' + - 'නොවැම්බර්' + - 'දෙසැම්බර්' + DAYS_OF_THE_WEEK: + - 'සඳුදා' + - 'අඟහරුවාදා' + - 'බදාදා' + - 'බ්රහස්පතින්දා' + - 'සිකුරාදා' + - 'සෙනසුරාදා' + - 'ඉරිදා' + YES: "ඔව්" + NO: "නැත" + CRON: + EVERY: සෑම + EVERY_HOUR: සෑම පැයකටම + EVERY_MINUTE: සෑම විනාඩියකටම + EVERY_DAY_OF_WEEK: සතියේ සෑම දිනකම + EVERY_DAY_OF_MONTH: මාසයේ සෑම දිනකම + EVERY_MONTH: සෑම මාසයකම + TEXT_PERIOD: සෑම + TEXT_MINS: ' පැයට පසු විනාඩි කින්' + TEXT_TIME: ' :ට' + TEXT_DOW: ' මත' + TEXT_MONTH: ' ' + TEXT_DOM: ' මත' + ERROR1: ටැගය %s සහාය නොදක්වයි! + ERROR2: නරක මූලද්රව්ය සංඛ්යාව + ERROR3: jquery_element jqCron සැකසුම් වලට සැකසිය යුතුය + ERROR4: හඳුනා නොගත් ප්‍රකාශනය diff --git a/system/languages/sk.yaml b/system/languages/sk.yaml new file mode 100644 index 0000000..9543239 --- /dev/null +++ b/system/languages/sk.yaml @@ -0,0 +1,144 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Chyba: Chybný frontmatter\n\nPath: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_PLURALS: + '/(quiz)$/i': '\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' + INFLECTOR_SINGULAR: + '/(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' + INFLECTOR_UNCOUNTABLE: + - 'vybavenie' + - 'informácie' + - 'ryža' + - 'peniaze' + - 'druhy' + - 'séria' + - 'ryba' + - 'ovce' + INFLECTOR_IRREGULAR: + 'person': 'ľudia' + 'man': 'muži' + 'child': 'deti' + 'sex': 'pohlavia' + 'move': 'pohyby' + INFLECTOR_ORDINALS: + 'default': '.' + 'first': '.' + 'second': '.' + 'third': '.' + NICETIME: + NO_DATE_PROVIDED: Neposkytnutý žiaden dátum + BAD_DATE: Nesprávny dátum + AGO: pred + FROM_NOW: odteraz + JUST_NOW: práve teraz + SECOND: sekunda + MINUTE: minúta + HOUR: hodina + DAY: deň + WEEK: týždeň + MONTH: mesiac + YEAR: rok + DECADE: desaťročie + SEC: sek + MIN: min + HR: hod + WK: t + MO: m + YR: r + DEC: dec + SECOND_PLURAL: sekúnd + MINUTE_PLURAL: minút + HOUR_PLURAL: hodín + DAY_PLURAL: dní + WEEK_PLURAL: týždňov + MONTH_PLURAL: mesiacov + YEAR_PLURAL: rokov + DECADE_PLURAL: dekád + SEC_PLURAL: sek + MIN_PLURAL: min + HR_PLURAL: hod + WK_PLURAL: t + MO_PLURAL: mes. + YR_PLURAL: rokov + DEC_PLURAL: dekád + FORM: + VALIDATION_FAIL: 'Overenie zlyhalo:' + INVALID_INPUT: 'Neplatný vstup v' + MISSING_REQUIRED_FIELD: 'Chýba vyžadované pole:' + MONTHS_OF_THE_YEAR: + - 'Január' + - 'Február' + - 'Marec' + - 'Apríl' + - 'Máj' + - 'Jún' + - 'Júl' + - 'August' + - 'September' + - 'Október' + - 'November' + - 'December' + DAYS_OF_THE_WEEK: + - 'Pondelok' + - 'Utorok' + - 'Streda' + - 'Štvrtok' + - 'Piatok' + - 'Sobota' + - 'Nedeľa' + CRON: + EVERY: každý + EVERY_HOUR: každú hodinu + EVERY_MINUTE: každú minútu + EVERY_DAY_OF_WEEK: každý deň v týždni + EVERY_DAY_OF_MONTH: každý deň v mesiaci + EVERY_MONTH: každý mesiac + TEXT_PERIOD: Každý + TEXT_MINS: ' at minute(s) past the hour' + TEXT_TIME: ' at :' + TEXT_DOW: ' on ' + TEXT_MONTH: ' of ' + TEXT_DOM: ' on ' + ERROR1: Tag %s nieje podporovaný! + ERROR2: Chybný počet položiek + ERROR3: jquery_element musí byť nastavený v nastaveniach pre jqCron + ERROR4: Neznámy výraz diff --git a/system/languages/sl.yaml b/system/languages/sl.yaml new file mode 100644 index 0000000..dc09814 --- /dev/null +++ b/system/languages/sl.yaml @@ -0,0 +1,85 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Napaka: Neveljavna Frontmatter\n\nPath: `%2$s`\n\n**%3$s ** \n\n```\n%4$s \n```" + INFLECTOR_UNCOUNTABLE: + - 'oprema' + - 'informacija' + - 'riž' + - 'denar' + - 'vrste' + - 'serija' + - 'riba' + - 'ovca' + INFLECTOR_IRREGULAR: + 'person': 'ljudje' + NICETIME: + NO_DATE_PROVIDED: Datum ni na voljo + BAD_DATE: Neveljaven datum + AGO: pred + FROM_NOW: od zdaj + SECOND: sekunda + MINUTE: minuta + HOUR: ura + DAY: dan + WEEK: teden + MONTH: mesec + YEAR: leto + DECADE: desetletje + SEC: sek + HR: ur + WK: T. + MO: m + YR: l + DEC: des + SECOND_PLURAL: sekund + MINUTE_PLURAL: minut + HOUR_PLURAL: ure + DAY_PLURAL: dnevi + WEEK_PLURAL: tednov + MONTH_PLURAL: mesecev + YEAR_PLURAL: leta + DECADE_PLURAL: desetletja + SEC_PLURAL: s + MIN_PLURAL: min + HR_PLURAL: ur + WK_PLURAL: t + MO_PLURAL: m + YR_PLURAL: l + DEC_PLURAL: des + FORM: + VALIDATION_FAIL: 'Preverjanje veljavnosti ni uspelo:' + INVALID_INPUT: 'Neveljaven vnos v' + MISSING_REQUIRED_FIELD: 'Manjka obvezno polje:' + MONTHS_OF_THE_YEAR: + - 'Januar' + - 'Februar' + - 'Marec' + - 'april' + - 'Maj' + - 'Junij' + - 'Julij' + - 'Avgust' + - 'september' + - 'Oktober' + - 'november' + - 'december' + DAYS_OF_THE_WEEK: + - 'Ponedeljek' + - 'Torek' + - 'Sreda' + - 'Četrtek' + - 'Petek' + - 'Sobota' + - 'Nedelja' + YES: "Da" + NO: "Ne" + CRON: + EVERY: vsak + EVERY_HOUR: vsako uro + EVERY_MINUTE: vsako minuto + EVERY_DAY_OF_WEEK: vsak dan v tednu + EVERY_DAY_OF_MONTH: vsak dan v mesecu + EVERY_MONTH: vsak mesec + ERROR1: Oznaka %s ni podprta! + ERROR2: Napačno število elementov. + ERROR4: Neznan izraz diff --git a/system/languages/sr.yaml b/system/languages/sr.yaml new file mode 100644 index 0000000..498d182 --- /dev/null +++ b/system/languages/sr.yaml @@ -0,0 +1,147 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\nнаслов: %1$s\n---\n\n# Грешка: неисправан Frontmatter\n\nПутања: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_PLURALS: + '/(quiz)$/i': '\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' + INFLECTOR_SINGULAR: + '/(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' + INFLECTOR_UNCOUNTABLE: + - 'опрема' + - 'информација' + - 'пиринач' + - 'новац' + - 'врсте' + - 'серије' + - 'риба' + - 'овца' + INFLECTOR_IRREGULAR: + 'person': 'особе' + 'man': 'људи' + 'child': 'деца' + 'sex': 'полови' + 'move': 'помери' + INFLECTOR_ORDINALS: + 'default': 'ти' + 'first': 'први' + 'second': 'други' + 'third': 'трећи' + NICETIME: + NO_DATE_PROVIDED: Нема датума + BAD_DATE: Погрешан датум + AGO: од пре + FROM_NOW: од сада + JUST_NOW: управо сада + SECOND: секунда + MINUTE: минута + HOUR: сат + DAY: дан + WEEK: недеља + MONTH: месец + YEAR: година + DECADE: декада + SEC: сек + MIN: мин + HR: сат + WK: нед + MO: мес + YR: год + DEC: дек + SECOND_PLURAL: секунди + MINUTE_PLURAL: минута + HOUR_PLURAL: сати + DAY_PLURAL: дана + WEEK_PLURAL: недеља + MONTH_PLURAL: месеци + YEAR_PLURAL: године(а) + DECADE_PLURAL: декаде(а) + SEC_PLURAL: сек + MIN_PLURAL: мин + HR_PLURAL: сати + WK_PLURAL: недеља + MO_PLURAL: месеци + YR_PLURAL: година + DEC_PLURAL: декада + FORM: + VALIDATION_FAIL: 'Провера неуспела:' + INVALID_INPUT: 'Неисправан унос у' + MISSING_REQUIRED_FIELD: 'Недостаје обавезн поље:' + XSS_ISSUES: "Потенцијална грешка у XSS-у детектована у пољу '%s' " + MONTHS_OF_THE_YEAR: + - 'Јануар' + - 'Фебруар' + - 'Март' + - 'Април' + - 'Мај' + - 'Јуни' + - 'Јули' + - 'Август' + - 'Септембар' + - 'Октобар' + - 'Новембар' + - 'Децембар' + DAYS_OF_THE_WEEK: + - 'Понедељак' + - 'Уторак' + - 'Среда' + - 'Четвртак' + - 'Петак' + - 'Субота' + - 'Недеља' + YES: "Да" + NO: "Не" + CRON: + EVERY: сваки + EVERY_HOUR: сваки сат + EVERY_MINUTE: сваки минут + EVERY_DAY_OF_WEEK: сваки дан у недељи + EVERY_DAY_OF_MONTH: сваки дан у месецу + EVERY_MONTH: сваки месец + TEXT_PERIOD: Сваки + TEXT_MINS: ' у минути(а) прошлог сата' + TEXT_TIME: ' у :' + TEXT_DOW: ' на ' + TEXT_MONTH: ' од ' + TEXT_DOM: ' на ' + ERROR1: Таг %s није подржан! + ERROR2: Погрешан број елемената + ERROR3: јquery_element би требао да буде постављен у jqCron подешавању + ERROR4: Непрепознат израз diff --git a/system/languages/sv.yaml b/system/languages/sv.yaml new file mode 100644 index 0000000..bf76bef --- /dev/null +++ b/system/languages/sv.yaml @@ -0,0 +1,100 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "--- titel: %1$s --- # Fel: Ogiltig Frontmatter-sökväg: `%2$s` **%3$s** ``` %4$s ```" + INFLECTOR_UNCOUNTABLE: + - 'utrustning' + - 'information' + - 'ris' + - 'pengar' + - 'arter' + - 'serier' + - 'fisk' + - 'får' + INFLECTOR_IRREGULAR: + 'person': 'personer' + 'man': 'män' + 'child': 'barn' + 'sex': 'kön' + 'move': 'flytta' + INFLECTOR_ORDINALS: + 'default': ':e' + 'first': ':a' + 'second': ':a' + 'third': ':e' + NICETIME: + NO_DATE_PROVIDED: Inget datum har angivits + BAD_DATE: Ogiltigt datum + AGO: sedan + FROM_NOW: fr.o.m nu + JUST_NOW: just nu + SECOND: sekund + MINUTE: minut + HOUR: timme + DAY: dag + WEEK: vecka + MONTH: månad + YEAR: år + DECADE: årtionde + SEC: sek + MIN: min + HR: t + WK: v + MO: m + YR: år + DEC: dec + SECOND_PLURAL: sekunder + MINUTE_PLURAL: minuter + HOUR_PLURAL: timmar + DAY_PLURAL: dagar + WEEK_PLURAL: veckor + MONTH_PLURAL: månader + YEAR_PLURAL: år + DECADE_PLURAL: årtionden + SEC_PLURAL: sek + MIN_PLURAL: min + HR_PLURAL: t + WK_PLURAL: v + MO_PLURAL: må + YR_PLURAL: år + DEC_PLURAL: dec + FORM: + VALIDATION_FAIL: 'Kontrollen misslyckades:' + INVALID_INPUT: 'Ogiltig indata i' + MISSING_REQUIRED_FIELD: 'Obligatoriskt fält måste fyllas i:' + MONTHS_OF_THE_YEAR: + - 'Januari' + - 'Februari' + - 'Mars' + - 'April' + - 'Maj' + - 'Juni' + - 'Juli' + - 'Augusti' + - 'September' + - 'Oktober' + - 'November' + - 'December' + DAYS_OF_THE_WEEK: + - 'Måndag' + - 'Tisdag' + - 'Onsdag' + - 'Torsdag' + - 'Fredag' + - 'Lördag' + - 'Söndag' + CRON: + EVERY: varje + EVERY_HOUR: varje timme + EVERY_MINUTE: varje minut + EVERY_DAY_OF_WEEK: varje veckodag + EVERY_DAY_OF_MONTH: alla månadens dagar + EVERY_MONTH: varje månad + TEXT_PERIOD: Varje + TEXT_MINS: ' timmens :e minut' + TEXT_TIME: ' kl :' + TEXT_DOW: ' ' + TEXT_MONTH: ' ' + TEXT_DOM: ' ' + ERROR1: Taggen %s stöds inte! + ERROR2: Ogiltigt antal element + ERROR4: Uttrycket känns inte igen diff --git a/system/languages/sw.yaml b/system/languages/sw.yaml new file mode 100644 index 0000000..9bb40d6 --- /dev/null +++ b/system/languages/sw.yaml @@ -0,0 +1,147 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\nkichwa: %1$s\n---\n\n# Kosa: Mbele ya Mbele\n\nNjia: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_PLURALS: + '/(quiz)$/i': '\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' + INFLECTOR_SINGULAR: + '/(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' + INFLECTOR_UNCOUNTABLE: + - 'vifaa' + - 'habari' + - 'mchele' + - 'pesa' + - 'spishi' + - 'mfululizo' + - 'samaki' + - 'kondoo' + INFLECTOR_IRREGULAR: + 'person': 'watu' + 'man': 'wanaume' + 'child': 'watoto' + 'sex': 'jinsia' + 'move': 'songa' + INFLECTOR_ORDINALS: + 'default': 'th' + 'first': 'st' + 'second': 'nd' + 'third': 'rd' + NICETIME: + NO_DATE_PROVIDED: Hakuna tarehe iliyotolewa + BAD_DATE: Tarehe mbaya + AGO: zilizopita + FROM_NOW: kuanzia sasa + JUST_NOW: sasa hivi + SECOND: pili + MINUTE: dakika + HOUR: saa + DAY: siku + WEEK: wiki + MONTH: mwezi + YEAR: mwaka + DECADE: muongo + SEC: sec + MIN: min + HR: hr + WK: wk + MO: mo + YR: yr + DEC: dec + SECOND_PLURAL: sekunde + MINUTE_PLURAL: dakika + HOUR_PLURAL: masaa + DAY_PLURAL: siku + WEEK_PLURAL: wiki + MONTH_PLURAL: miezi + YEAR_PLURAL: miaka + DECADE_PLURAL: miongo + SEC_PLURAL: secs + MIN_PLURAL: mins + HR_PLURAL: hrs + WK_PLURAL: wks + MO_PLURAL: mos + YR_PLURAL: yrs + DEC_PLURAL: decs + FORM: + VALIDATION_FAIL: ' Uthibitishaji umeshindwa: ' + INVALID_INPUT: 'Ingizo batili katika' + MISSING_REQUIRED_FIELD: 'Sehemu inayokosekana inahitajika:' + XSS_ISSUES: "Masuala yanayowezekana ya XSS yamegunduliwa katika uwanja wa '% s" + MONTHS_OF_THE_YEAR: + - 'Januari' + - 'Februari' + - 'Machi' + - 'Aprili' + - 'Mei' + - 'Juni' + - 'Julai' + - 'Agosti' + - 'Septemba' + - 'Oktoba' + - 'Novemba' + - 'Desemba' + DAYS_OF_THE_WEEK: + - 'Jumatatu' + - 'Jumanne' + - 'Jumatano' + - 'Alhamisi' + - 'Ijumaa' + - 'Jumamosi' + - 'Jumapili' + YES: "Ndiyo" + NO: "Hapana" + CRON: + EVERY: kila + EVERY_HOUR: kila saa + EVERY_MINUTE: kila dakika + EVERY_DAY_OF_WEEK: kila siku ya juma + EVERY_DAY_OF_MONTH: kila siku ya mwezi + EVERY_MONTH: kila mwezi + TEXT_PERIOD: Kila + TEXT_MINS: ' saa dakika (saa) zilizopita saa' + TEXT_TIME: ' saa : ' + TEXT_DOW: ' kwenye ' + TEXT_MONTH: ' ya ' + TEXT_DOM: ' kwenye ' + ERROR1: Lebo% s haitumiki! + ERROR2: Idadi mbaya ya vitu + ERROR3: Jquery_element inapaswa kuwekwa kwenye mipangilio ya jqCron + ERROR4: Maneno yasiyotambulika diff --git a/system/languages/th.yaml b/system/languages/th.yaml new file mode 100644 index 0000000..762f063 --- /dev/null +++ b/system/languages/th.yaml @@ -0,0 +1,147 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Error: Invalid Frontmatter\n\nPath: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_PLURALS: + '/(quiz)$/i': '\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' + INFLECTOR_SINGULAR: + '/(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' + INFLECTOR_UNCOUNTABLE: + - 'อุปกรณ์' + - 'ข้อมูล' + - 'ข้าว' + - 'เงิน' + - 'สายพันธุ์' + - 'ซีรีส์' + - 'ปลา' + - 'แกะ' + INFLECTOR_IRREGULAR: + 'person': 'คน' + 'man': 'ผู้ชาย' + 'child': 'เด็กเด็ก' + 'sex': 'เพศ' + 'move': 'ย้าย' + INFLECTOR_ORDINALS: + 'default': 'th' + 'first': 'st' + 'second': 'nd' + 'third': 'rd' + NICETIME: + NO_DATE_PROVIDED: ไม่มีวันที่ให้ + BAD_DATE: รูปแบบวันที่ผิด + AGO: ที่ผ่านมา + FROM_NOW: จากตอนนี้ + JUST_NOW: เมื่อกี้ + SECOND: วินาที + MINUTE: นาที + HOUR: ชั่วโมง + DAY: วัน + WEEK: สัปดาห์ + MONTH: เดือน + YEAR: ปี + DECADE: ทศวรรษที่ผ่านมา + SEC: วิ + MIN: นาที + HR: ชม. + WK: wk + MO: mo + YR: yr + DEC: dec + SECOND_PLURAL: วินาที + MINUTE_PLURAL: นาที + HOUR_PLURAL: ชั่วโมง + DAY_PLURAL: วัน + WEEK_PLURAL: สัปดาห์ + MONTH_PLURAL: เดือน + YEAR_PLURAL: ปี + DECADE_PLURAL: ทศวรรษที่ผ่านมา + SEC_PLURAL: วินาที + MIN_PLURAL: นาที + HR_PLURAL: ชั่วโมง + WK_PLURAL: wks + MO_PLURAL: mos + YR_PLURAL: ปี + DEC_PLURAL: decs + FORM: + VALIDATION_FAIL: 'ตรวจสอบล้มเหลว: ' + INVALID_INPUT: 'ป้อนข้อมูลไม่ถูกต้องใน' + MISSING_REQUIRED_FIELD: 'ขาดข้อมูลที่จำเป็น:' + XSS_ISSUES: "ตรวจพบปัญหา XSS ที่เป็นไปได้ในฟิลด์ '%s'" + MONTHS_OF_THE_YEAR: + - 'มกราคม' + - 'กุมภาพันธ์' + - 'มีนาคม' + - 'เมษายน' + - 'พฤษภาคม' + - 'มิถุนายน' + - 'กรกฏาคม' + - 'สิงหาคม' + - 'กันยายน' + - 'ตุลาคม' + - 'พฤศจิกายน' + - 'ธันวาคม' + DAYS_OF_THE_WEEK: + - 'จันทร์' + - 'อังคาร' + - 'พุธ' + - 'พฤหัสบดี' + - 'ศุกร์' + - 'เสาร์' + - 'อาทิตย์' + YES: "ใช่" + NO: "ไม่" + CRON: + EVERY: ทุก ๆ + EVERY_HOUR: ทุกชั่วโมง + EVERY_MINUTE: ทุกนาที + EVERY_DAY_OF_WEEK: ทุกวันในสัปดาห์ + EVERY_DAY_OF_MONTH: ทุกวันของเดือน + EVERY_MONTH: ทุกเดือน + TEXT_PERIOD: ทุก ๆ + TEXT_MINS: ' ที่ นาทีที่ผ่านไปแล้ว' + TEXT_TIME: ' เวลา :' + TEXT_DOW: ' บน ' + TEXT_MONTH: ' จาก ' + TEXT_DOM: ' บน ' + ERROR1: ไม่รองรับแท็ก %s! + ERROR2: จำนวนองค์ประกอบไม่ดี + ERROR3: ควรตั้งค่า jquery_element เป็นการตั้งค่า jqCron + ERROR4: นิพจน์ที่ไม่รู้จัก diff --git a/system/languages/tr.yaml b/system/languages/tr.yaml new file mode 100644 index 0000000..47f32db --- /dev/null +++ b/system/languages/tr.yaml @@ -0,0 +1,100 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\nBaşlık: %1$s\n---\n\n# Hata: Geçersiz Önbölüm\n\nYol: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_UNCOUNTABLE: + - 'ekipman' + - 'bilgi' + - 'pirinç' + - 'para' + - 'türler' + - 'seriler' + - 'balık' + - 'koyun' + INFLECTOR_IRREGULAR: + 'person': 'kişi' + 'man': 'erkek' + 'child': 'çocuklar' + 'sex': 'cinsiyet' + 'move': 'taşınmış' + INFLECTOR_ORDINALS: + 'default': '#F' + 'first': ' 1.' + 'second': ' 2.' + 'third': ' 3.' + NICETIME: + NO_DATE_PROVIDED: Sağlanan tarih yok + BAD_DATE: Yanlış tarih + AGO: önce + FROM_NOW: şu andan itibaren + JUST_NOW: şimdi + SECOND: saniye + MINUTE: dakika + HOUR: saat + DAY: gün + WEEK: hafta + MONTH: ay + YEAR: yıl + DECADE: onyıl + SEC: sn + MIN: dk + HR: sa + WK: hft + MO: ay + YR: yl + DEC: onyl + SECOND_PLURAL: saniye + MINUTE_PLURAL: dakika + HOUR_PLURAL: saat + DAY_PLURAL: gün + WEEK_PLURAL: hafta + MONTH_PLURAL: ay + YEAR_PLURAL: yıl + DECADE_PLURAL: onyıl + SEC_PLURAL: sn + MIN_PLURAL: dk + HR_PLURAL: sa + WK_PLURAL: hft + MO_PLURAL: ay + YR_PLURAL: yıl + DEC_PLURAL: onyl + FORM: + VALIDATION_FAIL: 'Doğrulama başarısız:' + INVALID_INPUT: 'Geçersiz bilgi girişi' + MISSING_REQUIRED_FIELD: 'Gerekli alan eksik:' + MONTHS_OF_THE_YEAR: + - 'Ocak' + - 'Şubat' + - 'Mart' + - 'Nisan' + - 'Mayıs' + - 'Haziran' + - 'Temmuz' + - 'Ağustos' + - 'Eylül' + - 'Ekim' + - 'Kasım' + - 'Aralık' + DAYS_OF_THE_WEEK: + - 'Pazartesi' + - 'Salı' + - 'Çarşamba' + - 'Perşembe' + - 'Cuma' + - 'Cumartesi' + - 'Pazar' + YES: "Evet" + NO: "Hayır" + CRON: + EVERY: her + EVERY_HOUR: saatte bir + EVERY_MINUTE: dakikada bir + EVERY_DAY_OF_WEEK: haftanın her günü + EVERY_DAY_OF_MONTH: ayın her günü + EVERY_MONTH: her ay + TEXT_PERIOD: Her + TEXT_MINS: ' saatin dakikasında' + TEXT_TIME: ' da' + ERROR1: Etiket %s desteklenmiyor! + ERROR2: Kötü eleman sayısı + ERROR3: jquery_element jqCron ayarları içinde tanımlanmalı + ERROR4: Tanınmayan ifade diff --git a/system/languages/uk.yaml b/system/languages/uk.yaml new file mode 100644 index 0000000..8a138a4 --- /dev/null +++ b/system/languages/uk.yaml @@ -0,0 +1,63 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Помилка: Недопустимий вміст\n\nPath: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + NICETIME: + NO_DATE_PROVIDED: Не вказана дата + BAD_DATE: Невірна дата + AGO: назад + FROM_NOW: відтепер + SECOND: секунда + MINUTE: хвилина + HOUR: година + DAY: день + WEEK: тиждень + MONTH: місяць + YEAR: рік + DECADE: десятиріччя + SEC: с + MIN: хв + HR: год + WK: тиж. + MO: міс. + YR: р. + DEC: рр. + SECOND_PLURAL: секунди + MINUTE_PLURAL: хвилини + HOUR_PLURAL: години + DAY_PLURAL: дні + WEEK_PLURAL: тижні + MONTH_PLURAL: місяці + YEAR_PLURAL: роки + DECADE_PLURAL: десятиріччя + SEC_PLURAL: с + MIN_PLURAL: хв + HR_PLURAL: год + WK_PLURAL: тиж. + MO_PLURAL: міс. + YR_PLURAL: рр. + DEC_PLURAL: рр. + FORM: + VALIDATION_FAIL: 'Перевірка не вдалася:' + INVALID_INPUT: 'Невірне введення в' + MISSING_REQUIRED_FIELD: 'Відсутнє обов''язкове поле:' + MONTHS_OF_THE_YEAR: + - 'Січень' + - 'Лютий' + - 'Березень' + - 'Квітень' + - 'Травень' + - 'Червень' + - 'Липень' + - 'Серпень' + - 'Вересень' + - 'Жовтень' + - 'Листопад' + - 'Грудень' + DAYS_OF_THE_WEEK: + - 'Понеділок' + - 'Вівторок' + - 'Середа' + - 'Четвер' + - 'П''ятниця' + - 'Субота' + - 'Неділя' diff --git a/system/languages/vi.yaml b/system/languages/vi.yaml new file mode 100644 index 0000000..9e3a0f4 --- /dev/null +++ b/system/languages/vi.yaml @@ -0,0 +1,63 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\ntiêu đề: %1$s\n---\n\n# Error: Trang không hợp lệ\n\nĐường dẫn: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + NICETIME: + NO_DATE_PROVIDED: Không có ngày được cung cấp + BAD_DATE: Ngày không hợp lệ + AGO: cách đây + FROM_NOW: từ bây giờ + SECOND: giây + MINUTE: phút + HOUR: giờ + DAY: ngày + WEEK: tuần + MONTH: tháng + YEAR: năm + DECADE: thập kỷ + SEC: giây + MIN: phút + HR: giờ + WK: tuần + MO: tháng + YR: năm + DEC: thập kỷ + SECOND_PLURAL: giây + MINUTE_PLURAL: phút + HOUR_PLURAL: giờ + DAY_PLURAL: ngày + WEEK_PLURAL: tuần + MONTH_PLURAL: tháng + YEAR_PLURAL: năm + DECADE_PLURAL: thập kỷ + SEC_PLURAL: giây + MIN_PLURAL: phút + HR_PLURAL: giờ + WK_PLURAL: tuần + MO_PLURAL: tháng + YR_PLURAL: năm + DEC_PLURAL: thập kỷ + FORM: + VALIDATION_FAIL: 'Xác nhận thất bại:' + INVALID_INPUT: 'Dữ liệu nhập không hợp lệ cho' + MISSING_REQUIRED_FIELD: 'Thiếu trường bắt buộc:' + MONTHS_OF_THE_YEAR: + - 'Tháng 1' + - 'Tháng 2' + - 'Tháng 3' + - 'Tháng 4' + - 'Tháng 5' + - 'Tháng 6' + - 'Tháng 7' + - 'Tháng 8' + - 'Tháng 9' + - 'Tháng 10' + - 'Tháng 11' + - 'Tháng 12' + DAYS_OF_THE_WEEK: + - 'Thứ 2' + - 'Thứ 3' + - 'Thứ 4' + - 'Thứ 5' + - 'Thứ 6' + - 'Thứ 7' + - 'Chủ Nhật' diff --git a/system/languages/zh-cn.yaml b/system/languages/zh-cn.yaml new file mode 100644 index 0000000..d1afaa4 --- /dev/null +++ b/system/languages/zh-cn.yaml @@ -0,0 +1,146 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\n标题: %1$s\n---\n\n# 错误:无效参数\n\n位置: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_PLURALS: + '/(quiz)$/i': '\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' + INFLECTOR_SINGULAR: + '/(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' + INFLECTOR_UNCOUNTABLE: + - '装备' + - '信息' + - '大米' + - '钱' + - '物种' + - '系列' + - '鱼' + - '羊' + INFLECTOR_IRREGULAR: + 'person': '人员' + 'man': '男人' + 'child': '儿童' + 'sex': '性别' + 'move': '移动' + INFLECTOR_ORDINALS: + 'default': 'th' + 'first': 'st' + 'second': 'md' + 'third': 'rd' + NICETIME: + NO_DATE_PROVIDED: 无日期信息 + BAD_DATE: 无效日期 + AGO: 前 + FROM_NOW: 距今 + JUST_NOW: 刚刚 + SECOND: 秒 + MINUTE: 分钟 + HOUR: 小时 + DAY: 天 + WEEK: 周 + MONTH: 月 + YEAR: 年 + DECADE: 十年 + SEC: 秒 + MIN: 分钟 + HR: 小时 + WK: 周 + MO: 月 + YR: 年 + DEC: 年代 + SECOND_PLURAL: 秒 + MINUTE_PLURAL: 分 + HOUR_PLURAL: 小时 + DAY_PLURAL: 天 + WEEK_PLURAL: 周 + MONTH_PLURAL: 月 + YEAR_PLURAL: 年 + DECADE_PLURAL: 十年 + SEC_PLURAL: 秒 + MIN_PLURAL: 分 + HR_PLURAL: 时 + WK_PLURAL: 周 + MO_PLURAL: 月 + YR_PLURAL: 年 + DEC_PLURAL: 年代 + FORM: + VALIDATION_FAIL: '验证失败:' + INVALID_INPUT: '无效输入' + MISSING_REQUIRED_FIELD: '必填字段缺失:' + MONTHS_OF_THE_YEAR: + - '1月' + - '2月' + - '3月' + - '4月' + - '5月' + - '6月' + - '7月' + - '8月' + - '9月' + - '10月' + - '11月' + - '12月' + DAYS_OF_THE_WEEK: + - '星期一' + - '星期二' + - '星期三' + - '星期四' + - '星期五' + - '星期六' + - '星期日' + YES: "是" + NO: "否" + CRON: + EVERY: 每隔 + EVERY_HOUR: 每小时 + EVERY_MINUTE: 每分钟 + EVERY_DAY_OF_WEEK: 一周中的每一天 + EVERY_DAY_OF_MONTH: 月份中的每一天 + EVERY_MONTH: 每月 + TEXT_PERIOD: 所有 + TEXT_MINS: ' 在 小时过后的分钟' + TEXT_TIME: ' 在 :' + TEXT_DOW: ' on ' + TEXT_MONTH: ' of ' + TEXT_DOM: ' on ' + ERROR1: 不支持分享类型 %s + ERROR2: 无效数字 + ERROR3: 请在 jqCron 设置中设定 jquery_element + ERROR4: 无法识别表达式 diff --git a/system/languages/zh-tw.yaml b/system/languages/zh-tw.yaml new file mode 100644 index 0000000..779cae9 --- /dev/null +++ b/system/languages/zh-tw.yaml @@ -0,0 +1,79 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# 錯誤: 不正確的 Frontmatter\n\n路徑: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + NICETIME: + NO_DATE_PROVIDED: 沒有提供日期 + BAD_DATE: 錯誤日期 + AGO: 之前 + FROM_NOW: 之後 + JUST_NOW: 剛剛 + SECOND: 秒 + MINUTE: 分 + HOUR: 小時 + DAY: 天 + WEEK: 週 + MONTH: 月 + YEAR: 年 + DECADE: 十年 + SEC: 秒 + MIN: 分 + HR: 小時 + WK: 週 + MO: 月 + YR: 年 + DEC: 十年 + SECOND_PLURAL: 秒 + MINUTE_PLURAL: 分 + HOUR_PLURAL: 小時 + DAY_PLURAL: 天 + WEEK_PLURAL: 週 + MONTH_PLURAL: 月 + YEAR_PLURAL: 年 + DECADE_PLURAL: 十年 + SEC_PLURAL: 秒 + MIN_PLURAL: 分 + HR_PLURAL: 時 + WK_PLURAL: 週 + MO_PLURAL: 月 + YR_PLURAL: 年 + DEC_PLURAL: 十年 + FORM: + VALIDATION_FAIL: '確驗證失敗:' + INVALID_INPUT: '無效輸入:' + MISSING_REQUIRED_FIELD: '遺漏必填欄位:' + MONTHS_OF_THE_YEAR: + - '一月' + - '二月' + - '三月' + - '四月' + - '五月' + - '六月' + - '七月' + - '八月' + - '九月' + - '十月' + - '十一月' + - '十二月' + DAYS_OF_THE_WEEK: + - '星期一' + - '星期二' + - '星期三' + - '星期四' + - '星期五' + - '星期六' + - '星期日' + YES: "是" + NO: "否" + CRON: + EVERY: 每 + EVERY_HOUR: 每小時 + EVERY_MINUTE: 每分鐘 + EVERY_DAY_OF_WEEK: 每一天 + EVERY_DAY_OF_MONTH: 每一天 + EVERY_MONTH: 每個月 + TEXT_PERIOD: 每 + TEXT_MINS: ' 的 分' + TEXT_TIME: ' :' + TEXT_DOW: ' 的 ' + TEXT_MONTH: ' 的 ' + TEXT_DOM: ' 的 ' diff --git a/system/languages/zh.yaml b/system/languages/zh.yaml new file mode 100644 index 0000000..d1afaa4 --- /dev/null +++ b/system/languages/zh.yaml @@ -0,0 +1,146 @@ +--- +GRAV: + FRONTMATTER_ERROR_PAGE: "---\n标题: %1$s\n---\n\n# 错误:无效参数\n\n位置: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```" + INFLECTOR_PLURALS: + '/(quiz)$/i': '\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' + INFLECTOR_SINGULAR: + '/(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' + INFLECTOR_UNCOUNTABLE: + - '装备' + - '信息' + - '大米' + - '钱' + - '物种' + - '系列' + - '鱼' + - '羊' + INFLECTOR_IRREGULAR: + 'person': '人员' + 'man': '男人' + 'child': '儿童' + 'sex': '性别' + 'move': '移动' + INFLECTOR_ORDINALS: + 'default': 'th' + 'first': 'st' + 'second': 'md' + 'third': 'rd' + NICETIME: + NO_DATE_PROVIDED: 无日期信息 + BAD_DATE: 无效日期 + AGO: 前 + FROM_NOW: 距今 + JUST_NOW: 刚刚 + SECOND: 秒 + MINUTE: 分钟 + HOUR: 小时 + DAY: 天 + WEEK: 周 + MONTH: 月 + YEAR: 年 + DECADE: 十年 + SEC: 秒 + MIN: 分钟 + HR: 小时 + WK: 周 + MO: 月 + YR: 年 + DEC: 年代 + SECOND_PLURAL: 秒 + MINUTE_PLURAL: 分 + HOUR_PLURAL: 小时 + DAY_PLURAL: 天 + WEEK_PLURAL: 周 + MONTH_PLURAL: 月 + YEAR_PLURAL: 年 + DECADE_PLURAL: 十年 + SEC_PLURAL: 秒 + MIN_PLURAL: 分 + HR_PLURAL: 时 + WK_PLURAL: 周 + MO_PLURAL: 月 + YR_PLURAL: 年 + DEC_PLURAL: 年代 + FORM: + VALIDATION_FAIL: '验证失败:' + INVALID_INPUT: '无效输入' + MISSING_REQUIRED_FIELD: '必填字段缺失:' + MONTHS_OF_THE_YEAR: + - '1月' + - '2月' + - '3月' + - '4月' + - '5月' + - '6月' + - '7月' + - '8月' + - '9月' + - '10月' + - '11月' + - '12月' + DAYS_OF_THE_WEEK: + - '星期一' + - '星期二' + - '星期三' + - '星期四' + - '星期五' + - '星期六' + - '星期日' + YES: "是" + NO: "否" + CRON: + EVERY: 每隔 + EVERY_HOUR: 每小时 + EVERY_MINUTE: 每分钟 + EVERY_DAY_OF_WEEK: 一周中的每一天 + EVERY_DAY_OF_MONTH: 月份中的每一天 + EVERY_MONTH: 每月 + TEXT_PERIOD: 所有 + TEXT_MINS: ' 在 小时过后的分钟' + TEXT_TIME: ' 在 :' + TEXT_DOW: ' on ' + TEXT_MONTH: ' of ' + TEXT_DOM: ' on ' + ERROR1: 不支持分享类型 %s + ERROR2: 无效数字 + ERROR3: 请在 jqCron 设置中设定 jquery_element + ERROR4: 无法识别表达式 diff --git a/system/pages/notfound.md b/system/pages/notfound.md new file mode 100644 index 0000000..9228664 --- /dev/null +++ b/system/pages/notfound.md @@ -0,0 +1,6 @@ +--- +title: Not Found +routable: false +notfound: true +expires: 0 +--- diff --git a/system/router.php b/system/router.php new file mode 100644 index 0000000..dc2cc04 --- /dev/null +++ b/system/router.php @@ -0,0 +1,55 @@ +load('example.xml'); + * foreach(new DOMLettersIterator($doc) as $letter) echo $letter; + * + * NB: If you only need characters without their position + * in the document, use DOMNode->textContent instead. + * + * @author porneL http://pornel.net + * @license Public Domain + * @url https://github.com/antoligy/dom-string-iterators + * + * @implements Iterator + */ +final class DOMLettersIterator implements Iterator +{ + /** @var DOMElement */ + private $start; + /** @var DOMElement|null */ + private $current; + /** @var int */ + private $offset = -1; + /** @var int|null */ + private $key; + /** @var array|null */ + private $letters; + + /** + * expects DOMElement or DOMDocument (see DOMDocument::load and DOMDocument::loadHTML) + * + * @param DOMNode $el + */ + public function __construct(DOMNode $el) + { + if ($el instanceof DOMDocument) { + $el = $el->documentElement; + } + + if (!$el instanceof DOMElement) { + throw new InvalidArgumentException('Invalid arguments, expected DOMElement or DOMDocument'); + } + + $this->start = $el; + } + + /** + * Returns position in text as DOMText node and character offset. + * (it's NOT a byte offset, you must use mb_substr() or similar to use this offset properly). + * node may be NULL if iterator has finished. + * + * @return array + */ + public function currentTextPosition(): array + { + return [$this->current, $this->offset]; + } + + /** + * Returns DOMElement that is currently being iterated or NULL if iterator has finished. + * + * @return DOMElement|null + */ + public function currentElement(): ?DOMElement + { + return $this->current ? $this->current->parentNode : null; + } + + // Implementation of Iterator interface + + /** + * @return int|null + */ + public function key(): ?int + { + return $this->key; + } + + /** + * @return void + */ + public function next(): void + { + if (null === $this->current) { + return; + } + + if ($this->current->nodeType === XML_TEXT_NODE || $this->current->nodeType === XML_CDATA_SECTION_NODE) { + if ($this->offset === -1) { + preg_match_all('/./us', $this->current->textContent, $m); + $this->letters = $m[0]; + } + + $this->offset++; + $this->key++; + if ($this->letters && $this->offset < count($this->letters)) { + return; + } + + $this->offset = -1; + } + + while ($this->current->nodeType === XML_ELEMENT_NODE && $this->current->firstChild) { + $this->current = $this->current->firstChild; + if ($this->current->nodeType === XML_TEXT_NODE || $this->current->nodeType === XML_CDATA_SECTION_NODE) { + $this->next(); + return; + } + } + + while (!$this->current->nextSibling && $this->current->parentNode) { + $this->current = $this->current->parentNode; + if ($this->current === $this->start) { + $this->current = null; + return; + } + } + + $this->current = $this->current->nextSibling; + + $this->next(); + } + + /** + * Return the current element + * @link https://php.net/manual/en/iterator.current.php + * + * @return string|null + */ + public function current(): ?string + { + return $this->letters ? $this->letters[$this->offset] : null; + } + + /** + * Checks if current position is valid + * @link https://php.net/manual/en/iterator.valid.php + * + * @return bool + */ + public function valid(): bool + { + return (bool)$this->current; + } + + /** + * @return void + */ + public function rewind(): void + { + $this->current = $this->start; + $this->offset = -1; + $this->key = 0; + $this->letters = []; + + $this->next(); + } +} + diff --git a/system/src/DOMWordsIterator.php b/system/src/DOMWordsIterator.php new file mode 100644 index 0000000..fb7c2e3 --- /dev/null +++ b/system/src/DOMWordsIterator.php @@ -0,0 +1,158 @@ +load('example.xml'); + * foreach(new DOMWordsIterator($doc) as $word) echo $word; + * + * @author pjgalbraith http://www.pjgalbraith.com + * @author porneL http://pornel.net (based on DOMLettersIterator available at http://pornel.net/source/domlettersiterator.php) + * @license Public Domain + * @url https://github.com/antoligy/dom-string-iterators + * + * @implements Iterator + */ + +final class DOMWordsIterator implements Iterator +{ + /** @var DOMElement */ + private $start; + /** @var DOMElement|null */ + private $current; + /** @var int */ + private $offset = -1; + /** @var int|null */ + private $key; + /** @var array>|null */ + private $words; + + /** + * expects DOMElement or DOMDocument (see DOMDocument::load and DOMDocument::loadHTML) + * + * @param DOMNode $el + */ + public function __construct(DOMNode $el) + { + if ($el instanceof DOMDocument) { + $el = $el->documentElement; + } + + if (!$el instanceof DOMElement) { + throw new InvalidArgumentException('Invalid arguments, expected DOMElement or DOMDocument'); + } + + $this->start = $el; + } + + /** + * Returns position in text as DOMText node and character offset. + * (it's NOT a byte offset, you must use mb_substr() or similar to use this offset properly). + * node may be NULL if iterator has finished. + * + * @return array + */ + public function currentWordPosition(): array + { + return [$this->current, $this->offset, $this->words]; + } + + /** + * Returns DOMElement that is currently being iterated or NULL if iterator has finished. + * + * @return DOMElement|null + */ + public function currentElement(): ?DOMElement + { + return $this->current ? $this->current->parentNode : null; + } + + // Implementation of Iterator interface + + /** + * Return the key of the current element + * @link https://php.net/manual/en/iterator.key.php + * @return int|null + */ + public function key(): ?int + { + return $this->key; + } + + /** + * @return void + */ + public function next(): void + { + if (null === $this->current) { + return; + } + + if ($this->current->nodeType === XML_TEXT_NODE || $this->current->nodeType === XML_CDATA_SECTION_NODE) { + if ($this->offset === -1) { + $this->words = preg_split("/[\n\r\t ]+/", $this->current->textContent, -1, PREG_SPLIT_NO_EMPTY|PREG_SPLIT_OFFSET_CAPTURE) ?: []; + } + $this->offset++; + + if ($this->words && $this->offset < count($this->words)) { + $this->key++; + return; + } + $this->offset = -1; + } + + while ($this->current->nodeType === XML_ELEMENT_NODE && $this->current->firstChild) { + $this->current = $this->current->firstChild; + if ($this->current->nodeType === XML_TEXT_NODE || $this->current->nodeType === XML_CDATA_SECTION_NODE) { + $this->next(); + return; + } + } + + while (!$this->current->nextSibling && $this->current->parentNode) { + $this->current = $this->current->parentNode; + if ($this->current === $this->start) { + $this->current = null; + return; + } + } + + $this->current = $this->current->nextSibling; + + $this->next(); + } + + /** + * Return the current element + * @link https://php.net/manual/en/iterator.current.php + * @return string|null + */ + public function current(): ?string + { + return $this->words ? (string)$this->words[$this->offset][0] : null; + } + + /** + * Checks if current position is valid + * @link https://php.net/manual/en/iterator.valid.php + * @return bool + */ + public function valid(): bool + { + return (bool)$this->current; + } + + public function rewind(): void + { + $this->current = $this->start; + $this->offset = -1; + $this->key = 0; + $this->words = []; + + $this->next(); + } +} diff --git a/system/src/Grav/Common/Assets.php b/system/src/Grav/Common/Assets.php new file mode 100644 index 0000000..afb4e63 --- /dev/null +++ b/system/src/Grav/Common/Assets.php @@ -0,0 +1,595 @@ +get('system.assets'); + + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + $this->assets_dir = $locator->findResource('asset://'); + $this->assets_url = $locator->findResource('asset://', false); + + $this->config($asset_config); + + // Register any preconfigured collections + foreach ((array) $this->collections as $name => $collection) { + $this->registerCollection($name, (array)$collection); + } + } + + /** + * Set up configuration options. + * + * All the class properties except 'js' and 'css' are accepted here. + * Also, an extra option 'autoload' may be passed containing an array of + * assets and/or collections that will be automatically added on startup. + * + * @param array $config Configurable options. + * @return $this + */ + public function config(array $config) + { + foreach ($config as $key => $value) { + if ($this->hasProperty($key)) { + $this->setProperty($key, $value); + } elseif (Utils::startsWith($key, 'css_') || Utils::startsWith($key, 'js_')) { + $this->pipeline_options[$key] = $value; + } + } + + // Add timestamp if it's enabled + if ($this->enable_asset_timestamp) { + $this->timestamp = Grav::instance()['cache']->getKey(); + } + + return $this; + } + + /** + * Add an asset or a collection of assets. + * + * It automatically detects the asset type (JavaScript, CSS or collection). + * You may add more than one asset passing an array as argument. + * + * @param string|string[] $asset + * @return $this + */ + public function add($asset) + { + if (!$asset) { + return $this; + } + + $args = func_get_args(); + + // More than one asset + if (is_array($asset)) { + foreach ($asset as $index => $location) { + $params = array_slice($args, 1); + if (is_array($location)) { + $params = array_shift($params); + if (is_numeric($params)) { + $params = [ 'priority' => $params ]; + } + $params = [array_replace_recursive([], $location, $params)]; + $location = $index; + } + + $params = array_merge([$location], $params); + call_user_func_array([$this, 'add'], $params); + } + } elseif (isset($this->collections[$asset])) { + array_shift($args); + $args = array_merge([$this->collections[$asset]], $args); + call_user_func_array([$this, 'add'], $args); + } else { + // Get extension + $path = parse_url($asset, PHP_URL_PATH); + $extension = $path ? Utils::pathinfo($path, PATHINFO_EXTENSION) : ''; + + // JavaScript or CSS + if ($extension !== '') { + $extension = strtolower($extension); + if ($extension === 'css') { + call_user_func_array([$this, 'addCss'], $args); + } elseif ($extension === 'js') { + call_user_func_array([$this, 'addJs'], $args); + } elseif ($extension === 'mjs') { + call_user_func_array([$this, 'addJsModule'], $args); + } + } + } + + return $this; + } + + /** + * @param string $collection + * @param string $type + * @param string|string[] $asset + * @param array $options + * @return $this + */ + protected function addType($collection, $type, $asset, $options) + { + if (is_array($asset)) { + foreach ($asset as $index => $location) { + $assetOptions = $options; + if (is_array($location)) { + $assetOptions = array_replace_recursive([], $options, $location); + $location = $index; + } + $this->addType($collection, $type, $location, $assetOptions); + } + + return $this; + } + + if ($this->isValidType($type) && isset($this->collections[$asset])) { + $this->addType($collection, $type, $this->collections[$asset], $options); + return $this; + } + + // If pipeline disabled, set to position if provided, else after + if (isset($options['pipeline'])) { + if ($options['pipeline'] === false) { + + $exclude_type = $this->getBaseType($type); + + $excludes = strtolower($exclude_type . '_pipeline_before_excludes'); + if ($this->{$excludes}) { + $default = 'after'; + } else { + $default = 'before'; + } + + $options['position'] = $options['position'] ?? $default; + } + + unset($options['pipeline']); + } + + // Add timestamp + $timestamp_override = $options['timestamp'] ?? true; + + if (filter_var($timestamp_override, FILTER_VALIDATE_BOOLEAN)) { + $options['timestamp'] = $this->timestamp; + } else { + $options['timestamp'] = null; + } + + // Set order + $group = $options['group'] ?? 'head'; + $position = $options['position'] ?? 'pipeline'; + + $orderKey = "{$type}|{$group}|{$position}"; + if (!isset($this->order[$orderKey])) { + $this->order[$orderKey] = 0; + } + + $options['order'] = $this->order[$orderKey]++; + + // Create asset of correct type + $asset_object = new $type(); + + // If exists + if ($asset_object->init($asset, $options)) { + $this->$collection[md5($asset)] = $asset_object; + } + + return $this; + } + + /** + * Add a CSS asset or a collection of assets. + * + * @return $this + */ + public function addLink($asset) + { + return $this->addType($this::LINK_COLLECTION, $this::LINK_TYPE, $asset, $this->unifyLegacyArguments(func_get_args(), $this::LINK_TYPE)); + } + + /** + * Add a CSS asset or a collection of assets. + * + * @return $this + */ + public function addCss($asset) + { + return $this->addType($this::CSS_COLLECTION, $this::CSS_TYPE, $asset, $this->unifyLegacyArguments(func_get_args(), $this::CSS_TYPE)); + } + + /** + * Add an Inline CSS asset or a collection of assets. + * + * @return $this + */ + public function addInlineCss($asset) + { + return $this->addType($this::CSS_COLLECTION, $this::INLINE_CSS_TYPE, $asset, $this->unifyLegacyArguments(func_get_args(), $this::INLINE_CSS_TYPE)); + } + + /** + * Add a JS asset or a collection of assets. + * + * @return $this + */ + public function addJs($asset) + { + return $this->addType($this::JS_COLLECTION, $this::JS_TYPE, $asset, $this->unifyLegacyArguments(func_get_args(), $this::JS_TYPE)); + } + + /** + * Add an Inline JS asset or a collection of assets. + * + * @return $this + */ + public function addInlineJs($asset) + { + return $this->addType($this::JS_COLLECTION, $this::INLINE_JS_TYPE, $asset, $this->unifyLegacyArguments(func_get_args(), $this::INLINE_JS_TYPE)); + } + + /** + * Add a JS asset or a collection of assets. + * + * @return $this + */ + public function addJsModule($asset) + { + return $this->addType($this::JS_MODULE_COLLECTION, $this::JS_MODULE_TYPE, $asset, $this->unifyLegacyArguments(func_get_args(), $this::JS_MODULE_TYPE)); + } + + /** + * Add an Inline JS asset or a collection of assets. + * + * @return $this + */ + public function addInlineJsModule($asset) + { + return $this->addType($this::JS_MODULE_COLLECTION, $this::INLINE_JS_MODULE_TYPE, $asset, $this->unifyLegacyArguments(func_get_args(), $this::INLINE_JS_MODULE_TYPE)); + } + + /** + * Add/replace collection. + * + * @param string $collectionName + * @param array $assets + * @param bool $overwrite + * @return $this + */ + public function registerCollection($collectionName, array $assets, $overwrite = false) + { + if ($overwrite || !isset($this->collections[$collectionName])) { + $this->collections[$collectionName] = $assets; + } + + return $this; + } + + /** + * @param array $assets + * @param string $key + * @param string $value + * @param bool $sort + * @return array|false + */ + protected function filterAssets($assets, $key, $value, $sort = false) + { + $results = array_filter($assets, function ($asset) use ($key, $value) { + + if ($key === 'position' && $value === 'pipeline') { + $type = $asset->getType(); + if ($type === 'jsmodule') { + $type = 'js_module'; + } + + if ($asset->getRemote() && $this->{strtolower($type) . '_pipeline_include_externals'} === false && $asset['position'] === 'pipeline') { + if ($this->{strtolower($type) . '_pipeline_before_excludes'}) { + $asset->setPosition('after'); + } else { + $asset->setPosition('before'); + } + return false; + } + } + + if ($asset[$key] === $value) { + return true; + } + return false; + }); + + if ($sort && !empty($results)) { + $results = $this->sortAssets($results); + } + + + return $results; + } + + /** + * @param array $assets + * @return array + */ + protected function sortAssets($assets) + { + uasort($assets, static function ($a, $b) { + return $b['priority'] <=> $a['priority'] ?: $a['order'] <=> $b['order']; + }); + + return $assets; + } + + /** + * @param string $type + * @param string $group + * @param array $attributes + * @return string + */ + public function render($type, $group = 'head', $attributes = []) + { + $before_output = ''; + $pipeline_output = ''; + $after_output = ''; + + $assets = 'assets_' . $type; + $pipeline_enabled = $type . '_pipeline'; + $render_pipeline = 'render' . ucfirst($type); + + $group_assets = $this->filterAssets($this->$assets, 'group', $group); + $pipeline_assets = $this->filterAssets($group_assets, 'position', 'pipeline', true); + $before_assets = $this->filterAssets($group_assets, 'position', 'before', true); + $after_assets = $this->filterAssets($group_assets, 'position', 'after', true); + + // Pipeline + if ($this->{$pipeline_enabled} ?? false) { + $options = array_merge($this->pipeline_options, ['timestamp' => $this->timestamp]); + + $pipeline = new Pipeline($options); + $pipeline_output = $pipeline->$render_pipeline($pipeline_assets, $group, $attributes); + } else { + foreach ($pipeline_assets as $asset) { + $pipeline_output .= $asset->render(); + } + } + + // Before Pipeline + foreach ($before_assets as $asset) { + $before_output .= $asset->render(); + } + + // After Pipeline + foreach ($after_assets as $asset) { + $after_output .= $asset->render(); + } + + return $before_output . $pipeline_output . $after_output; + } + + + /** + * Build the CSS link tags. + * + * @param string $group name of the group + * @param array $attributes + * @return string + */ + public function css($group = 'head', $attributes = [], $include_link = true) + { + $output = ''; + + if ($include_link) { + $output = $this->link($group, $attributes); + } + + $output .= $this->render(self::CSS, $group, $attributes); + + return $output; + } + + /** + * Build the CSS link tags. + * + * @param string $group name of the group + * @param array $attributes + * @return string + */ + public function link($group = 'head', $attributes = []) + { + return $this->render(self::LINK, $group, $attributes); + } + + /** + * Build the JavaScript script tags. + * + * @param string $group name of the group + * @param array $attributes + * @return string + */ + public function js($group = 'head', $attributes = [], $include_js_module = true) + { + $output = $this->render(self::JS, $group, $attributes); + + if ($include_js_module) { + $output .= $this->jsModule($group, $attributes); + } + + return $output; + } + + /** + * Build the Javascript Modules tags + * + * @param string $group + * @param array $attributes + * @return string + */ + public function jsModule($group = 'head', $attributes = []) + { + return $this->render(self::JS_MODULE, $group, $attributes); + } + + /** + * @param string $group + * @param array $attributes + * @return string + */ + public function all($group = 'head', $attributes = []) + { + $output = $this->css($group, $attributes, false); + $output .= $this->link($group, $attributes); + $output .= $this->js($group, $attributes, false); + $output .= $this->jsModule($group, $attributes); + return $output; + } + + /** + * @param class-string $type + * @return bool + */ + protected function isValidType($type) + { + return in_array($type, [self::CSS_TYPE, self::JS_TYPE, self::JS_MODULE_TYPE]); + } + + /** + * @param class-string $type + * @return string + */ + protected function getBaseType($type) + { + switch ($type) { + case $this::JS_TYPE: + case $this::INLINE_JS_TYPE: + $base_type = $this::JS; + break; + case $this::JS_MODULE_TYPE: + case $this::INLINE_JS_MODULE_TYPE: + $base_type = $this::JS_MODULE; + break; + default: + $base_type = $this::CSS; + } + + return $base_type; + } +} diff --git a/system/src/Grav/Common/Assets/BaseAsset.php b/system/src/Grav/Common/Assets/BaseAsset.php new file mode 100644 index 0000000..29082f8 --- /dev/null +++ b/system/src/Grav/Common/Assets/BaseAsset.php @@ -0,0 +1,283 @@ + 'head', + 'position' => 'pipeline', + 'priority' => 10, + 'modified' => null, + 'asset' => null + ]; + + // Merge base defaults + $elements = array_merge($base_config, $elements); + + parent::__construct($elements, $key); + } + + /** + * @param string|false $asset + * @param array $options + * @return $this|false + */ + public function init($asset, $options) + { + if (!$asset) { + return false; + } + + $config = Grav::instance()['config']; + $uri = Grav::instance()['uri']; + + // set attributes + foreach ($options as $key => $value) { + if ($this->hasProperty($key)) { + $this->setProperty($key, $value); + } else { + $this->attributes[$key] = $value; + } + } + + // Force priority to be an int + $this->priority = (int) $this->priority; + + // Do some special stuff for CSS/JS (not inline) + if (!Utils::startsWith($this->getType(), 'inline')) { + $this->base_url = rtrim($uri->rootUrl($config->get('system.absolute_urls')), '/') . '/'; + $this->remote = static::isRemoteLink($asset); + + // Move this to render? + if (!$this->remote) { + $asset_parts = parse_url($asset); + if (isset($asset_parts['query'])) { + $this->query = $asset_parts['query']; + unset($asset_parts['query']); + $asset = Uri::buildUrl($asset_parts); + } + + $locator = Grav::instance()['locator']; + + if ($locator->isStream($asset)) { + $path = $locator->findResource($asset, true); + } else { + $path = GRAV_WEBROOT . $asset; + } + + // If local file is missing return + if ($path === false) { + return false; + } + + $file = new SplFileInfo($path); + + $asset = $this->buildLocalLink($file->getPathname()); + + $this->modified = $file->isFile() ? $file->getMTime() : false; + } + } + + $this->asset = $asset; + + return $this; + } + + /** + * @return string|false + */ + public function getAsset() + { + return $this->asset; + } + + /** + * @return bool + */ + public function getRemote() + { + return $this->remote; + } + + /** + * @param string $position + * @return $this + */ + public function setPosition($position) + { + $this->position = $position; + + return $this; + } + + /** + * Receive asset location and return the SRI integrity hash + * + * @param string $input + * @return string + */ + public static function integrityHash($input) + { + $grav = Grav::instance(); + $uri = $grav['uri']; + + $assetsConfig = $grav['config']->get('system.assets'); + + if (!self::isRemoteLink($input) && !empty($assetsConfig['enable_asset_sri']) && $assetsConfig['enable_asset_sri']) { + $input = preg_replace('#^' . $uri->rootUrl() . '#', '', $input); + $asset = File::instance(GRAV_WEBROOT . $input); + + if ($asset->exists()) { + $dataToHash = $asset->content(); + $hash = hash('sha256', $dataToHash, true); + $hash_base64 = base64_encode($hash); + + return ' integrity="sha256-' . $hash_base64 . '"'; + } + } + + return ''; + } + + + /** + * + * Get the last modification time of asset + * + * @param string $asset the asset string reference + * + * @return string the last modifcation time or false on error + */ +// protected function getLastModificationTime($asset) +// { +// $file = GRAV_WEBROOT . $asset; +// if (Grav::instance()['locator']->isStream($asset)) { +// $file = $this->buildLocalLink($asset, true); +// } +// +// return file_exists($file) ? filemtime($file) : false; +// } + + /** + * + * Build local links including grav asset shortcodes + * + * @param string $asset the asset string reference + * + * @return string|false the final link url to the asset + */ + protected function buildLocalLink($asset) + { + if ($asset) { + return $this->base_url . ltrim(Utils::replaceFirstOccurrence(GRAV_WEBROOT, '', $asset), '/'); + } + return false; + } + + + /** + * Implements JsonSerializable interface. + * + * @return array + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ['type' => $this->getType(), 'elements' => $this->getElements()]; + } + + /** + * Placeholder for AssetUtilsTrait method + * + * @param string $file + * @param string $dir + * @param bool $local + * @return string + */ + protected function cssRewrite($file, $dir, $local) + { + return ''; + } + + /** + * Finds relative JS urls() and rewrites the URL with an absolute one + * + * @param string $file the css source file + * @param string $dir local relative path to the css file + * @param bool $local is this a local or remote asset + * @return string + */ + protected function jsRewrite($file, $dir, $local) + { + return ''; + } +} diff --git a/system/src/Grav/Common/Assets/BlockAssets.php b/system/src/Grav/Common/Assets/BlockAssets.php new file mode 100644 index 0000000..211a6a1 --- /dev/null +++ b/system/src/Grav/Common/Assets/BlockAssets.php @@ -0,0 +1,207 @@ +getAssets(); + foreach ($types as $type => $groups) { + switch ($type) { + case 'frameworks': + static::registerFrameworks($assets, $groups); + break; + case 'styles': + static::registerStyles($assets, $groups); + break; + case 'scripts': + static::registerScripts($assets, $groups); + break; + case 'links': + static::registerLinks($assets, $groups); + break; + case 'html': + static::registerHtml($assets, $groups); + break; + } + } + } + + /** + * @param Assets $assets + * @param array $list + * @return void + */ + protected static function registerFrameworks(Assets $assets, array $list): void + { + if ($list) { + throw new \RuntimeException('Not Implemented'); + } + } + + /** + * @param Assets $assets + * @param array $groups + * @return void + */ + protected static function registerStyles(Assets $assets, array $groups): void + { + $grav = Grav::instance(); + + /** @var Config $config */ + $config = $grav['config']; + + foreach ($groups as $group => $styles) { + foreach ($styles as $style) { + switch ($style[':type']) { + case 'file': + $options = [ + 'priority' => $style[':priority'], + 'group' => $group, + 'type' => $style['type'], + 'media' => $style['media'] + ] + $style['element']; + + $assets->addCss(static::getRelativeUrl($style['href'], $config->get('system.assets.css_pipeline')), $options); + break; + case 'inline': + $options = [ + 'priority' => $style[':priority'], + 'group' => $group, + 'type' => $style['type'], + ] + $style['element']; + + $assets->addInlineCss($style['content'], $options); + break; + } + } + } + } + + /** + * @param Assets $assets + * @param array $groups + * @return void + */ + protected static function registerScripts(Assets $assets, array $groups): void + { + $grav = Grav::instance(); + + /** @var Config $config */ + $config = $grav['config']; + + foreach ($groups as $group => $scripts) { + $group = $group === 'footer' ? 'bottom' : $group; + + foreach ($scripts as $script) { + switch ($script[':type']) { + case 'file': + $options = [ + 'group' => $group, + 'priority' => $script[':priority'], + 'src' => $script['src'], + 'type' => $script['type'], + 'loading' => $script['loading'], + 'defer' => $script['defer'], + 'async' => $script['async'], + 'handle' => $script['handle'] + ] + $script['element']; + + $assets->addJs(static::getRelativeUrl($script['src'], $config->get('system.assets.js_pipeline')), $options); + break; + case 'inline': + $options = [ + 'priority' => $script[':priority'], + 'group' => $group, + 'type' => $script['type'], + 'loading' => $script['loading'] + ] + $script['element']; + + $assets->addInlineJs($script['content'], $options); + break; + } + } + } + } + + /** + * @param Assets $assets + * @param array $groups + * @return void + */ + protected static function registerLinks(Assets $assets, array $groups): void + { + foreach ($groups as $group => $links) { + foreach ($links as $link) { + $href = $link['href']; + $options = [ + 'group' => $group, + 'priority' => $link[':priority'], + 'rel' => $link['rel'], + ] + $link['element']; + + $assets->addLink($href, $options); + } + } + } + + /** + * @param Assets $assets + * @param array $groups + * @return void + */ + protected static function registerHtml(Assets $assets, array $groups): void + { + if ($groups) { + throw new \RuntimeException('Not Implemented'); + } + } + + /** + * @param string $url + * @param bool $pipeline + * @return string + */ + protected static function getRelativeUrl($url, $pipeline) + { + $grav = Grav::instance(); + + $base = rtrim($grav['base_url'], '/') ?: '/'; + + if (strpos($url, $base) === 0) { + if ($pipeline) { + // Remove file timestamp if CSS pipeline has been enabled. + $url = preg_replace('|[?#].*|', '', $url); + } + + return substr($url, strlen($base) - 1); + } + return $url; + } +} diff --git a/system/src/Grav/Common/Assets/Css.php b/system/src/Grav/Common/Assets/Css.php new file mode 100644 index 0000000..b7c1c21 --- /dev/null +++ b/system/src/Grav/Common/Assets/Css.php @@ -0,0 +1,52 @@ + 'css', + 'attributes' => [ + 'type' => 'text/css', + 'rel' => 'stylesheet' + ] + ]; + + $merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements); + + parent::__construct($merged_attributes, $key); + } + + /** + * @return string + */ + public function render() + { + if (isset($this->attributes['loading']) && $this->attributes['loading'] === 'inline') { + $buffer = $this->gatherLinks([$this], self::CSS_ASSET); + return "\n"; + } + + return 'renderAttributes() . $this->integrityHash($this->asset) . ">\n"; + } +} diff --git a/system/src/Grav/Common/Assets/InlineCss.php b/system/src/Grav/Common/Assets/InlineCss.php new file mode 100644 index 0000000..1f31448 --- /dev/null +++ b/system/src/Grav/Common/Assets/InlineCss.php @@ -0,0 +1,44 @@ + 'css', + 'position' => 'after' + ]; + + $merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements); + + parent::__construct($merged_attributes, $key); + } + + /** + * @return string + */ + public function render() + { + return 'renderAttributes(). ">\n" . trim($this->asset) . "\n\n"; + } +} diff --git a/system/src/Grav/Common/Assets/InlineJs.php b/system/src/Grav/Common/Assets/InlineJs.php new file mode 100644 index 0000000..bf5837c --- /dev/null +++ b/system/src/Grav/Common/Assets/InlineJs.php @@ -0,0 +1,44 @@ + 'js', + 'position' => 'after' + ]; + + $merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements); + + parent::__construct($merged_attributes, $key); + } + + /** + * @return string + */ + public function render() + { + return 'renderAttributes(). ">\n" . trim($this->asset) . "\n\n"; + } +} diff --git a/system/src/Grav/Common/Assets/InlineJsModule.php b/system/src/Grav/Common/Assets/InlineJsModule.php new file mode 100644 index 0000000..091117c --- /dev/null +++ b/system/src/Grav/Common/Assets/InlineJsModule.php @@ -0,0 +1,46 @@ + 'js_module', + 'attributes' => ['type' => 'module'], + 'position' => 'after' + ]; + + $merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements); + + parent::__construct($merged_attributes, $key); + } + + /** + * @return string + */ + public function render() + { + return 'renderAttributes(). ">\n" . trim($this->asset) . "\n\n"; + } + +} diff --git a/system/src/Grav/Common/Assets/Js.php b/system/src/Grav/Common/Assets/Js.php new file mode 100644 index 0000000..e68eb43 --- /dev/null +++ b/system/src/Grav/Common/Assets/Js.php @@ -0,0 +1,48 @@ + 'js', + ]; + + $merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements); + + parent::__construct($merged_attributes, $key); + } + + /** + * @return string + */ + public function render() + { + if (isset($this->attributes['loading']) && $this->attributes['loading'] === 'inline') { + $buffer = $this->gatherLinks([$this], self::JS_ASSET); + return 'renderAttributes() . ">\n" . trim($buffer) . "\n\n"; + } + + return '\n"; + } +} diff --git a/system/src/Grav/Common/Assets/JsModule.php b/system/src/Grav/Common/Assets/JsModule.php new file mode 100644 index 0000000..419e984 --- /dev/null +++ b/system/src/Grav/Common/Assets/JsModule.php @@ -0,0 +1,49 @@ + 'js_module', + 'attributes' => ['type' => 'module'] + ]; + + $merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements); + + parent::__construct($merged_attributes, $key); + } + + /** + * @return string + */ + public function render() + { + if (isset($this->attributes['loading']) && $this->attributes['loading'] === 'inline') { + $buffer = $this->gatherLinks([$this], self::JS_MODULE_ASSET); + return 'renderAttributes() . ">\n" . trim($buffer) . "\n\n"; + } + + return '\n"; + } +} diff --git a/system/src/Grav/Common/Assets/Link.php b/system/src/Grav/Common/Assets/Link.php new file mode 100644 index 0000000..975d153 --- /dev/null +++ b/system/src/Grav/Common/Assets/Link.php @@ -0,0 +1,43 @@ + 'link', + ]; + + $merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements); + + parent::__construct($merged_attributes, $key); + } + + /** + * @return string + */ + public function render() + { + return 'renderAttributes() . $this->integrityHash($this->asset) . ">\n"; + } +} diff --git a/system/src/Grav/Common/Assets/Pipeline.php b/system/src/Grav/Common/Assets/Pipeline.php new file mode 100644 index 0000000..e740012 --- /dev/null +++ b/system/src/Grav/Common/Assets/Pipeline.php @@ -0,0 +1,347 @@ +base_url = rtrim($uri->rootUrl($config->get('system.absolute_urls')), '/') . '/'; + $this->assets_dir = $locator->findResource('asset://'); + if (!$this->assets_dir) { + // Attempt to create assets folder if it doesn't exist yet. + $this->assets_dir = $locator->findResource('asset://', true, true); + Folder::mkdir($this->assets_dir); + $locator->clearCache(); + } + + $this->assets_url = $locator->findResource('asset://', false); + } + + /** + * Minify and concatenate CSS + * + * @param array $assets + * @param string $group + * @param array $attributes + * @return bool|string URL or generated content if available, else false + */ + public function renderCss($assets, $group, $attributes = []) + { + // temporary list of assets to pipeline + $inline_group = false; + + if (array_key_exists('loading', $attributes) && $attributes['loading'] === 'inline') { + $inline_group = true; + unset($attributes['loading']); + } + + // Store Attributes + $this->attributes = array_merge(['type' => 'text/css', 'rel' => 'stylesheet'], $attributes); + + // Compute uid based on assets and timestamp + $json_assets = json_encode($assets); + $uid = md5($json_assets . (int)$this->css_minify . (int)$this->css_rewrite . $group); + $file = $uid . '.css'; + $relative_path = "{$this->base_url}{$this->assets_url}/{$file}"; + + $filepath = "{$this->assets_dir}/{$file}"; + if (file_exists($filepath)) { + $buffer = file_get_contents($filepath) . "\n"; + } else { + //if nothing found get out of here! + if (empty($assets)) { + return false; + } + + // Concatenate files + $buffer = $this->gatherLinks($assets, self::CSS_ASSET); + + // Minify if required + if ($this->shouldMinify('css')) { + $minifier = new CSS(); + $minifier->add($buffer); + $buffer = $minifier->minify(); + } + + // Write file + if (trim($buffer) !== '') { + file_put_contents($filepath, $buffer); + } + } + + if ($inline_group) { + $output = "\n"; + } else { + $this->asset = $relative_path; + $output = 'renderAttributes() . BaseAsset::integrityHash($this->asset) . ">\n"; + } + + return $output; + } + + /** + * Minify and concatenate JS files. + * + * @param array $assets + * @param string $group + * @param array $attributes + * @return bool|string URL or generated content if available, else false + */ + public function renderJs($assets, $group, $attributes = [], $type = self::JS_ASSET) + { + // temporary list of assets to pipeline + $inline_group = false; + + if (array_key_exists('loading', $attributes) && $attributes['loading'] === 'inline') { + $inline_group = true; + unset($attributes['loading']); + } + + // Store Attributes + $this->attributes = $attributes; + + // Compute uid based on assets and timestamp + $json_assets = json_encode($assets); + $uid = md5($json_assets . $this->js_minify . $group); + $file = $uid . '.js'; + $relative_path = "{$this->base_url}{$this->assets_url}/{$file}"; + + $filepath = "{$this->assets_dir}/{$file}"; + if (file_exists($filepath)) { + $buffer = file_get_contents($filepath) . "\n"; + } else { + //if nothing found get out of here! + if (empty($assets)) { + return false; + } + + // Concatenate files + $buffer = $this->gatherLinks($assets, $type); + + // Minify if required + if ($this->shouldMinify('js')) { + $minifier = new JS(); + $minifier->add($buffer); + $buffer = $minifier->minify(); + } + + // Write file + if (trim($buffer) !== '') { + file_put_contents($filepath, $buffer); + } + } + + if ($inline_group) { + $output = 'renderAttributes(). ">\n" . $buffer . "\n\n"; + } else { + $this->asset = $relative_path; + $output = '\n"; + } + + return $output; + } + + /** + * Minify and concatenate JS files. + * + * @param array $assets + * @param string $group + * @param array $attributes + * @return bool|string URL or generated content if available, else false + */ + public function renderJs_Module($assets, $group, $attributes = []) + { + $attributes['type'] = 'module'; + return $this->renderJs($assets, $group, $attributes, self::JS_MODULE_ASSET); + } + + /** + * Finds relative CSS urls() and rewrites the URL with an absolute one + * + * @param string $file the css source file + * @param string $dir , $local relative path to the css file + * @param bool $local is this a local or remote asset + * @return string + */ + protected function cssRewrite($file, $dir, $local) + { + // Strip any sourcemap comments + $file = preg_replace(self::CSS_SOURCEMAP_REGEX, '', $file); + + // Find any css url() elements, grab the URLs and calculate an absolute path + // Then replace the old url with the new one + $file = (string)preg_replace_callback(self::CSS_URL_REGEX, function ($matches) use ($dir, $local) { + $isImport = count($matches) > 3 && $matches[3] === '@import'; + + if ($isImport) { + $old_url = $matches[5]; + } else { + $old_url = $matches[2]; + } + + // Ensure link is not rooted to web server, a data URL, or to a remote host + if (preg_match(self::FIRST_FORWARDSLASH_REGEX, $old_url) || Utils::startsWith($old_url, 'data:') || $this->isRemoteLink($old_url)) { + return $matches[0]; + } + + // clean leading / + $old_url = Utils::normalizePath($dir . '/' . $old_url); + if (preg_match(self::FIRST_FORWARDSLASH_REGEX, $old_url)) { + $old_url = ltrim($old_url, '/'); + } + + $new_url = ($local ? $this->base_url : '') . $old_url; + + if ($isImport) { + return str_replace($matches[5], $new_url, $matches[0]); + } else { + return str_replace($matches[2], $new_url, $matches[0]); + } + }, $file); + + return $file; + } + + /** + * Finds relative JS urls() and rewrites the URL with an absolute one + * + * @param string $file the css source file + * @param string $dir local relative path to the css file + * @param bool $local is this a local or remote asset + * @return string + */ + protected function jsRewrite($file, $dir, $local) + { + // Find any js import elements, grab the URLs and calculate an absolute path + // Then replace the old url with the new one + $file = (string)preg_replace_callback(self::JS_IMPORT_REGEX, function ($matches) use ($dir, $local) { + + $old_url = $matches[1]; + + // Ensure link is not rooted to web server, a data URL, or to a remote host + if (preg_match(self::FIRST_FORWARDSLASH_REGEX, $old_url) || $this->isRemoteLink($old_url)) { + return $matches[0]; + } + + // clean leading / + $old_url = Utils::normalizePath($dir . '/' . $old_url); + $old_url = str_replace('/./', '/', $old_url); + if (preg_match(self::FIRST_FORWARDSLASH_REGEX, $old_url)) { + $old_url = ltrim($old_url, '/'); + } + + $new_url = ($local ? $this->base_url : '') . $old_url; + + return str_replace($matches[1], $new_url, $matches[0]); + }, $file); + + return $file; + } + + /** + * @param string $type + * @return bool + */ + private function shouldMinify($type = 'css') + { + $check = $type . '_minify'; + $win_check = $type . '_minify_windows'; + + $minify = (bool) $this->$check; + + // If this is a Windows server, and minify_windows is false (default value) skip the + // minification process because it will cause Apache to die/crash due to insufficient + // ThreadStackSize in httpd.conf - See: https://bugs.php.net/bug.php?id=47689 + if (stripos(php_uname('s'), 'WIN') === 0 && !$this->{$win_check}) { + $minify = false; + } + + return $minify; + } +} diff --git a/system/src/Grav/Common/Assets/Traits/AssetUtilsTrait.php b/system/src/Grav/Common/Assets/Traits/AssetUtilsTrait.php new file mode 100644 index 0000000..5b6de9c --- /dev/null +++ b/system/src/Grav/Common/Assets/Traits/AssetUtilsTrait.php @@ -0,0 +1,215 @@ +rootUrl(true); + + // Sanity check for local URLs with absolute URL's enabled + if (Utils::startsWith($link, $base)) { + return false; + } + + return (0 === strpos($link, 'http://') || 0 === strpos($link, 'https://') || 0 === strpos($link, '//')); + } + + /** + * Download and concatenate the content of several links. + * + * @param array $assets + * @param int $type + * @return string + */ + protected function gatherLinks(array $assets, int $type = self::CSS_ASSET): string + { + $buffer = ''; + foreach ($assets as $asset) { + $local = true; + + $link = $asset->getAsset(); + $relative_path = $link; + + if (static::isRemoteLink($link)) { + $local = false; + if (0 === strpos($link, '//')) { + $link = 'http:' . $link; + } + $relative_dir = dirname($relative_path); + } else { + // Fix to remove relative dir if grav is in one + if (($this->base_url !== '/') && Utils::startsWith($relative_path, $this->base_url)) { + $base_url = '#' . preg_quote($this->base_url, '#') . '#'; + $relative_path = ltrim(preg_replace($base_url, '/', $link, 1), '/'); + } + + $relative_dir = dirname($relative_path); + $link = GRAV_ROOT . '/' . $relative_path; + } + + // TODO: looks like this is not being used. + $file = $this->fetch_command instanceof Closure ? @$this->fetch_command->__invoke($link) : @file_get_contents($link); + + // No file found, skip it... + if ($file === false) { + continue; + } + + // Double check last character being + if ($type === self::JS_ASSET || $type === self::JS_MODULE_ASSET) { + $file = rtrim($file, ' ;') . ';'; + } + + // If this is CSS + the file is local + rewrite enabled + if ($type === self::CSS_ASSET && $this->css_rewrite) { + $file = $this->cssRewrite($file, $relative_dir, $local); + } + + if ($type === self::JS_MODULE_ASSET) { + $file = $this->jsRewrite($file, $relative_dir, $local); + } + + $file = rtrim($file) . PHP_EOL; + $buffer .= $file; + } + + // Pull out @imports and move to top + if ($type === self::CSS_ASSET) { + $buffer = $this->moveImports($buffer); + } + + return $buffer; + } + + /** + * Moves @import statements to the top of the file per the CSS specification + * + * @param string $file the file containing the combined CSS files + * @return string the modified file with any @imports at the top of the file + */ + protected function moveImports($file) + { + $regex = '{@import.*?["\']([^"\']+)["\'].*?;}'; + + $imports = []; + + $file = (string)preg_replace_callback($regex, static function ($matches) use (&$imports) { + $imports[] = $matches[0]; + + return ''; + }, $file); + + return implode("\n", $imports) . "\n\n" . $file; + } + + /** + * + * Build an HTML attribute string from an array. + * + * @return string + */ + protected function renderAttributes() + { + $html = ''; + $no_key = ['loading']; + + foreach ($this->attributes as $key => $value) { + if ($value === null) { + continue; + } + + if (is_numeric($key)) { + $key = $value; + } + if (is_array($value)) { + $value = implode(' ', $value); + } + + if (in_array($key, $no_key, true)) { + $element = htmlentities($value, ENT_QUOTES, 'UTF-8', false); + } else { + $element = $key . '="' . htmlentities($value, ENT_QUOTES, 'UTF-8', false) . '"'; + } + + $html .= ' ' . $element; + } + + return $html; + } + + /** + * Render Querystring + * + * @param string|null $asset + * @return string + */ + protected function renderQueryString($asset = null) + { + $querystring = ''; + + $asset = $asset ?? $this->asset; + $attributes = $this->attributes; + + if (!empty($this->query)) { + if (Utils::contains($asset, '?')) { + $querystring .= '&' . $this->query; + } else { + $querystring .= '?' . $this->query; + } + } + + if ($this->timestamp) { + if ($querystring || Utils::contains($asset, '?')) { + $querystring .= '&' . $this->timestamp; + } else { + $querystring .= '?' . $this->timestamp; + } + } + + return $querystring; + } +} diff --git a/system/src/Grav/Common/Assets/Traits/LegacyAssetsTrait.php b/system/src/Grav/Common/Assets/Traits/LegacyAssetsTrait.php new file mode 100644 index 0000000..99ac726 --- /dev/null +++ b/system/src/Grav/Common/Assets/Traits/LegacyAssetsTrait.php @@ -0,0 +1,137 @@ + null, 'pipeline' => true, 'loading' => null, 'group' => null]; + $arguments = $this->createArgumentsFromLegacy($args, $defaults); + break; + + case (Assets::INLINE_JS_TYPE): + $defaults = ['priority' => null, 'group' => null, 'attributes' => null]; + $arguments = $this->createArgumentsFromLegacy($args, $defaults); + + // special case to handle old attributes being passed in + if (isset($arguments['attributes'])) { + $old_attributes = $arguments['attributes']; + if (is_array($old_attributes)) { + $arguments = array_merge($arguments, $old_attributes); + } else { + $arguments['type'] = $old_attributes; + } + } + unset($arguments['attributes']); + + break; + + case (Assets::INLINE_CSS_TYPE): + $defaults = ['priority' => null, 'group' => null]; + $arguments = $this->createArgumentsFromLegacy($args, $defaults); + break; + + default: + case (Assets::CSS_TYPE): + $defaults = ['priority' => null, 'pipeline' => true, 'group' => null, 'loading' => null]; + $arguments = $this->createArgumentsFromLegacy($args, $defaults); + } + + return $arguments; + } + + /** + * @param array $args + * @param array $defaults + * @return array + */ + protected function createArgumentsFromLegacy(array $args, array $defaults) + { + // Remove arguments with old default values. + $arguments = []; + foreach ($args as $arg) { + $default = current($defaults); + if ($arg !== $default) { + $arguments[key($defaults)] = $arg; + } + next($defaults); + } + + return $arguments; + } + + /** + * Convenience wrapper for async loading of JavaScript + * + * @param string|array $asset + * @param int $priority + * @param bool $pipeline + * @param string $group name of the group + * @return Assets + * @deprecated Please use dynamic method with ['loading' => 'async']. + */ + public function addAsyncJs($asset, $priority = 10, $pipeline = true, $group = 'head') + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use dynamic method with [\'loading\' => \'async\']', E_USER_DEPRECATED); + + return $this->addJs($asset, $priority, $pipeline, 'async', $group); + } + + /** + * Convenience wrapper for deferred loading of JavaScript + * + * @param string|array $asset + * @param int $priority + * @param bool $pipeline + * @param string $group name of the group + * @return Assets + * @deprecated Please use dynamic method with ['loading' => 'defer']. + */ + public function addDeferJs($asset, $priority = 10, $pipeline = true, $group = 'head') + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use dynamic method with [\'loading\' => \'defer\']', E_USER_DEPRECATED); + + return $this->addJs($asset, $priority, $pipeline, 'defer', $group); + } +} diff --git a/system/src/Grav/Common/Assets/Traits/TestingAssetsTrait.php b/system/src/Grav/Common/Assets/Traits/TestingAssetsTrait.php new file mode 100644 index 0000000..6b264a1 --- /dev/null +++ b/system/src/Grav/Common/Assets/Traits/TestingAssetsTrait.php @@ -0,0 +1,350 @@ +collections[$asset]) || isset($this->assets_css[$asset]) || isset($this->assets_js[$asset]); + } + + /** + * Return the array of all the registered collections + * + * @return array + */ + public function getCollections() + { + return $this->collections; + } + + /** + * Set the array of collections explicitly + * + * @param array $collections + * @return $this + */ + public function setCollection($collections) + { + $this->collections = $collections; + + return $this; + } + + /** + * Return the array of all the registered CSS assets + * If a $key is provided, it will try to return only that asset + * else it will return null + * + * @param string|null $key the asset key + * @return array + */ + public function getCss($key = null) + { + if (null !== $key) { + $asset_key = md5($key); + + return $this->assets_css[$asset_key] ?? null; + } + + return $this->assets_css; + } + + /** + * Return the array of all the registered JS assets + * If a $key is provided, it will try to return only that asset + * else it will return null + * + * @param string|null $key the asset key + * @return array + */ + public function getJs($key = null) + { + if (null !== $key) { + $asset_key = md5($key); + + return $this->assets_js[$asset_key] ?? null; + } + + return $this->assets_js; + } + + /** + * Set the whole array of CSS assets + * + * @param array $css + * @return $this + */ + public function setCss($css) + { + $this->assets_css = $css; + + return $this; + } + + /** + * Set the whole array of JS assets + * + * @param array $js + * @return $this + */ + public function setJs($js) + { + $this->assets_js = $js; + + return $this; + } + + /** + * Removes an item from the CSS array if set + * + * @param string $key The asset key + * @return $this + */ + public function removeCss($key) + { + $asset_key = md5($key); + if (isset($this->assets_css[$asset_key])) { + unset($this->assets_css[$asset_key]); + } + + return $this; + } + + /** + * Removes an item from the JS array if set + * + * @param string $key The asset key + * @return $this + */ + public function removeJs($key) + { + $asset_key = md5($key); + if (isset($this->assets_js[$asset_key])) { + unset($this->assets_js[$asset_key]); + } + + return $this; + } + + /** + * Sets the state of CSS Pipeline + * + * @param bool $value + * @return $this + */ + public function setCssPipeline($value) + { + $this->css_pipeline = (bool)$value; + + return $this; + } + + /** + * Sets the state of JS Pipeline + * + * @param bool $value + * @return $this + */ + public function setJsPipeline($value) + { + $this->js_pipeline = (bool)$value; + + return $this; + } + + /** + * Reset all assets. + * + * @return $this + */ + public function reset() + { + $this->resetCss(); + $this->resetJs(); + $this->setCssPipeline(false); + $this->setJsPipeline(false); + $this->order = []; + + return $this; + } + + /** + * Reset JavaScript assets. + * + * @return $this + */ + public function resetJs() + { + $this->assets_js = []; + + return $this; + } + + /** + * Reset CSS assets. + * + * @return $this + */ + public function resetCss() + { + $this->assets_css = []; + + return $this; + } + + /** + * Explicitly set's a timestamp for assets + * + * @param string|int $value + */ + public function setTimestamp($value) + { + $this->timestamp = $value; + } + + /** + * Get the timestamp for assets + * + * @param bool $include_join + * @return string|null + */ + public function getTimestamp($include_join = true) + { + if ($this->timestamp) { + return $include_join ? '?' . $this->timestamp : $this->timestamp; + } + + return null; + } + + /** + * Add all assets matching $pattern within $directory. + * + * @param string $directory Relative to the Grav root path, or a stream identifier + * @param string $pattern (regex) + * @return $this + */ + public function addDir($directory, $pattern = self::DEFAULT_REGEX) + { + $root_dir = GRAV_ROOT; + + // Check if $directory is a stream. + if (strpos($directory, '://')) { + $directory = Grav::instance()['locator']->findResource($directory, null); + } + + // Get files + $files = $this->rglob($root_dir . DIRECTORY_SEPARATOR . $directory, $pattern, $root_dir . '/'); + + // No luck? Nothing to do + if (!$files) { + return $this; + } + + // Add CSS files + if ($pattern === self::CSS_REGEX) { + foreach ($files as $file) { + $this->addCss($file); + } + + return $this; + } + + // Add JavaScript files + if ($pattern === self::JS_REGEX) { + foreach ($files as $file) { + $this->addJs($file); + } + + return $this; + } + + // Add JavaScript Module files + if ($pattern === self::JS_MODULE_REGEX) { + foreach ($files as $file) { + $this->addJsModule($file); + } + + return $this; + } + + // Unknown pattern. + foreach ($files as $asset) { + $this->add($asset); + } + + return $this; + } + + /** + * Add all JavaScript assets within $directory + * + * @param string $directory Relative to the Grav root path, or a stream identifier + * @return $this + */ + public function addDirJs($directory) + { + return $this->addDir($directory, self::JS_REGEX); + } + + /** + * Add all CSS assets within $directory + * + * @param string $directory Relative to the Grav root path, or a stream identifier + * @return $this + */ + public function addDirCss($directory) + { + return $this->addDir($directory, self::CSS_REGEX); + } + + /** + * Recursively get files matching $pattern within $directory. + * + * @param string $directory + * @param string $pattern (regex) + * @param string|null $ltrim Will be trimmed from the left of the file path + * @return array + */ + protected function rglob($directory, $pattern, $ltrim = null) + { + $iterator = new RegexIterator(new RecursiveIteratorIterator(new RecursiveDirectoryIterator( + $directory, + FilesystemIterator::SKIP_DOTS + )), $pattern); + $offset = strlen($ltrim); + $files = []; + + foreach ($iterator as $file) { + $files[] = substr($file->getPathname(), $offset); + } + + return $files; + } +} diff --git a/system/src/Grav/Common/Backup/Backups.php b/system/src/Grav/Common/Backup/Backups.php new file mode 100644 index 0000000..31fc42c --- /dev/null +++ b/system/src/Grav/Common/Backup/Backups.php @@ -0,0 +1,325 @@ +addListener('onSchedulerInitialized', [$this, 'onSchedulerInitialized']); + + $grav->fireEvent('onBackupsInitialized', new Event(['backups' => $this])); + } + + /** + * @return void + */ + public function setup() + { + if (null === static::$backup_dir) { + $grav = Grav::instance(); + static::$backup_dir = $grav['locator']->findResource('backup://', true, true); + Folder::create(static::$backup_dir); + } + } + + /** + * @param Event $event + * @return void + */ + public function onSchedulerInitialized(Event $event) + { + $grav = Grav::instance(); + + /** @var Scheduler $scheduler */ + $scheduler = $event['scheduler']; + + /** @var Inflector $inflector */ + $inflector = $grav['inflector']; + + foreach (static::getBackupProfiles() as $id => $profile) { + $at = $profile['schedule_at']; + $name = $inflector::hyphenize($profile['name']); + $logs = 'logs/backup-' . $name . '.out'; + /** @var Job $job */ + $job = $scheduler->addFunction('Grav\Common\Backup\Backups::backup', [$id], $name); + $job->at($at); + $job->output($logs); + $job->backlink('/tools/backups'); + } + } + + /** + * @param string $backup + * @param string $base_url + * @return string + */ + public function getBackupDownloadUrl($backup, $base_url) + { + $param_sep = Grav::instance()['config']->get('system.param_sep', ':'); + $download = urlencode(base64_encode(Utils::basename($backup))); + $url = rtrim(Grav::instance()['uri']->rootUrl(true), '/') . '/' . trim( + $base_url, + '/' + ) . '/task' . $param_sep . 'backup/download' . $param_sep . $download . '/admin-nonce' . $param_sep . Utils::getNonce('admin-form'); + + return $url; + } + + /** + * @return array + */ + public static function getBackupProfiles() + { + return Grav::instance()['config']->get('backups.profiles'); + } + + /** + * @return array + */ + public static function getPurgeConfig() + { + return Grav::instance()['config']->get('backups.purge'); + } + + /** + * @return array + */ + public function getBackupNames() + { + return array_column(static::getBackupProfiles(), 'name'); + } + + /** + * @return float|int + */ + public static function getTotalBackupsSize() + { + $backups = static::getAvailableBackups(); + + return $backups ? array_sum(array_column($backups, 'size')) : 0; + } + + /** + * @param bool $force + * @return array + */ + public static function getAvailableBackups($force = false) + { + if ($force || null === static::$backups) { + static::$backups = []; + + $grav = Grav::instance(); + $backups_itr = new GlobIterator(static::$backup_dir . '/*.zip', FilesystemIterator::KEY_AS_FILENAME); + $inflector = $grav['inflector']; + $long_date_format = DATE_RFC2822; + + /** + * @var string $name + * @var SplFileInfo $file + */ + foreach ($backups_itr as $name => $file) { + if (preg_match(static::BACKUP_FILENAME_REGEXZ, $name, $matches)) { + $date = DateTime::createFromFormat(static::BACKUP_DATE_FORMAT, $matches[2]); + $timestamp = $date->getTimestamp(); + $backup = new stdClass(); + $backup->title = $inflector->titleize($matches[1]); + $backup->time = $date; + $backup->date = $date->format($long_date_format); + $backup->filename = $name; + $backup->path = $file->getPathname(); + $backup->size = $file->getSize(); + static::$backups[$timestamp] = $backup; + } + } + // Reverse Key Sort to get in reverse date order + krsort(static::$backups); + } + + return static::$backups; + } + + /** + * Backup + * + * @param int $id + * @param callable|null $status + * @return string|null + */ + public static function backup($id = 0, callable $status = null) + { + $grav = Grav::instance(); + + $profiles = static::getBackupProfiles(); + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + + if (isset($profiles[$id])) { + $backup = (object) $profiles[$id]; + } else { + throw new RuntimeException('No backups defined...'); + } + + $name = $grav['inflector']->underscorize($backup->name); + $date = date(static::BACKUP_DATE_FORMAT, time()); + $filename = trim($name, '_') . '--' . $date . '.zip'; + $destination = static::$backup_dir . DS . $filename; + $max_execution_time = ini_set('max_execution_time', '600'); + $backup_root = $backup->root; + + if ($locator->isStream($backup_root)) { + $backup_root = $locator->findResource($backup_root); + } else { + $backup_root = rtrim(GRAV_ROOT . $backup_root, DS) ?: DS; + } + + if (!$backup_root || !file_exists($backup_root)) { + throw new RuntimeException("Backup location: {$backup_root} does not exist..."); + } + + $options = [ + 'exclude_files' => static::convertExclude($backup->exclude_files ?? ''), + 'exclude_paths' => static::convertExclude($backup->exclude_paths ?? ''), + ]; + + $archiver = Archiver::create('zip'); + $archiver->setArchive($destination)->setOptions($options)->compress($backup_root, $status)->addEmptyFolders($options['exclude_paths'], $status); + + $status && $status([ + 'type' => 'message', + 'message' => 'Done...', + ]); + + $status && $status([ + 'type' => 'progress', + 'complete' => true + ]); + + if ($max_execution_time !== false) { + ini_set('max_execution_time', $max_execution_time); + } + + // Log the backup + $grav['log']->notice('Backup Created: ' . $destination); + + // Fire Finished event + $grav->fireEvent('onBackupFinished', new Event(['backup' => $destination])); + + // Purge anything required + static::purge(); + + // Log + $log = JsonFile::instance($locator->findResource("log://backup.log", true, true)); + $log->content([ + 'time' => time(), + 'location' => $destination + ]); + $log->save(); + + return $destination; + } + + /** + * @return void + * @throws Exception + */ + public static function purge() + { + $purge_config = static::getPurgeConfig(); + $trigger = $purge_config['trigger']; + $backups = static::getAvailableBackups(true); + + switch ($trigger) { + case 'number': + $backups_count = count($backups); + if ($backups_count > $purge_config['max_backups_count']) { + $last = end($backups); + unlink($last->path); + static::purge(); + } + break; + + case 'time': + $last = end($backups); + $now = new DateTime(); + $interval = $now->diff($last->time); + if ($interval->days > $purge_config['max_backups_time']) { + unlink($last->path); + static::purge(); + } + break; + + default: + $used_space = static::getTotalBackupsSize(); + $max_space = $purge_config['max_backups_space'] * 1024 * 1024 * 1024; + if ($used_space > $max_space) { + $last = end($backups); + unlink($last->path); + static::purge(); + } + break; + } + } + + /** + * @param string $exclude + * @return array + */ + protected static function convertExclude($exclude) + { + // Split by newlines, commas, or multiple spaces + $lines = preg_split("/[\r\n,]+|[\s]{2,}/", $exclude); + // Remove empty values and trim + $lines = array_filter(array_map('trim', $lines)); + + return array_map('trim', $lines, array_fill(0, count($lines), '/')); + } +} diff --git a/system/src/Grav/Common/Browser.php b/system/src/Grav/Common/Browser.php new file mode 100644 index 0000000..c383865 --- /dev/null +++ b/system/src/Grav/Common/Browser.php @@ -0,0 +1,153 @@ +useragent = parse_user_agent(); + } catch (InvalidArgumentException $e) { + $this->useragent = parse_user_agent("Mozilla/5.0 (compatible; Unknown;)"); + } + } + + /** + * Get the current browser identifier + * + * Currently detected browsers: + * + * Android Browser + * BlackBerry Browser + * Camino + * Kindle / Silk + * Firefox / Iceweasel + * Safari + * Internet Explorer + * IEMobile + * Chrome + * Opera + * Midori + * Vivaldi + * TizenBrowser + * Lynx + * Wget + * Curl + * + * @return string the lowercase browser name + */ + public function getBrowser() + { + return strtolower($this->useragent['browser']); + } + + /** + * Get the current platform identifier + * + * Currently detected platforms: + * + * Desktop + * -> Windows + * -> Linux + * -> Macintosh + * -> Chrome OS + * Mobile + * -> Android + * -> iPhone + * -> iPad / iPod Touch + * -> Windows Phone OS + * -> Kindle + * -> Kindle Fire + * -> BlackBerry + * -> Playbook + * -> Tizen + * Console + * -> Nintendo 3DS + * -> New Nintendo 3DS + * -> Nintendo Wii + * -> Nintendo WiiU + * -> PlayStation 3 + * -> PlayStation 4 + * -> PlayStation Vita + * -> Xbox 360 + * -> Xbox One + * + * @return string the lowercase platform name + */ + public function getPlatform() + { + return strtolower($this->useragent['platform']); + } + + /** + * Get the current full version identifier + * + * @return string the browser full version identifier + */ + public function getLongVersion() + { + return $this->useragent['version']; + } + + /** + * Get the current major version identifier + * + * @return int the browser major version identifier + */ + public function getVersion() + { + $version = explode('.', $this->getLongVersion()); + + return (int)$version[0]; + } + + /** + * Determine if the request comes from a human, or from a bot/crawler + * + * @return bool + */ + public function isHuman() + { + $browser = $this->getBrowser(); + if (empty($browser)) { + return false; + } + + if (preg_match('~(bot|crawl)~i', $browser)) { + return false; + } + + return true; + } + + /** + * Determine if “Do Not Track” is set by browser + * @see https://www.w3.org/TR/tracking-dnt/ + * + * @return bool + */ + public function isTrackable(): bool + { + return !(isset($_SERVER['HTTP_DNT']) && $_SERVER['HTTP_DNT'] === '1'); + } +} diff --git a/system/src/Grav/Common/Cache.php b/system/src/Grav/Common/Cache.php new file mode 100644 index 0000000..c7afcbe --- /dev/null +++ b/system/src/Grav/Common/Cache.php @@ -0,0 +1,743 @@ +init($grav); + } + + /** + * Initialization that sets a base key and the driver based on configuration settings + * + * @param Grav $grav + * @return void + */ + public function init(Grav $grav) + { + $this->config = $grav['config']; + $this->now = time(); + + if (null === $this->enabled) { + $this->enabled = (bool)$this->config->get('system.cache.enabled'); + } + + /** @var Uri $uri */ + $uri = $grav['uri']; + + $prefix = $this->config->get('system.cache.prefix'); + $uniqueness = substr(md5($uri->rootUrl(true) . $this->config->key() . GRAV_VERSION), 2, 8); + + // Cache key allows us to invalidate all cache on configuration changes. + $this->key = ($prefix ?: 'g') . '-' . $uniqueness; + $this->cache_dir = $grav['locator']->findResource('cache://doctrine/' . $uniqueness, true, true); + $this->driver_setting = $this->config->get('system.cache.driver'); + $this->driver = $this->getCacheDriver(); + $this->driver->setNamespace($this->key); + + /** @var EventDispatcher $dispatcher */ + $dispatcher = Grav::instance()['events']; + $dispatcher->addListener('onSchedulerInitialized', [$this, 'onSchedulerInitialized']); + } + + /** + * @return CacheInterface + */ + public function getSimpleCache() + { + if (null === $this->simpleCache) { + $cache = new \Grav\Framework\Cache\Adapter\DoctrineCache($this->driver, '', $this->getLifetime()); + + // Disable cache key validation. + $cache->setValidation(false); + + $this->simpleCache = $cache; + } + + return $this->simpleCache; + } + + /** + * Deletes old cache files based on age + * + * @return int + */ + public function purgeOldCache() + { + // Get the max age for cache files from config (default 30 days) + $max_age_days = $this->config->get('system.cache.purge_max_age_days', 30); + $max_age_seconds = $max_age_days * 86400; // Convert days to seconds + $now = time(); + $count = 0; + + // First, clean up old orphaned cache directories (not the current one) + $cache_dir = dirname($this->cache_dir); + $current = Utils::basename($this->cache_dir); + + foreach (new DirectoryIterator($cache_dir) as $file) { + $dir = $file->getBasename(); + if ($dir === $current || $file->isDot() || $file->isFile()) { + continue; + } + + // Check if directory is old and empty or very old (90+ days) + $dir_age = $now - $file->getMTime(); + if ($dir_age > 7776000) { // 90 days + Folder::delete($file->getPathname()); + $count++; + } + } + + // Now clean up old cache files within the current cache directory + if (is_dir($this->cache_dir)) { + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($this->cache_dir, \RecursiveDirectoryIterator::SKIP_DOTS), + \RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ($iterator as $file) { + if ($file->isFile()) { + $file_age = $now - $file->getMTime(); + if ($file_age > $max_age_seconds) { + @unlink($file->getPathname()); + $count++; + } + } + } + } + + // Also clean up old files in compiled cache + $grav = Grav::instance(); + $compiled_dir = $this->config->get('system.cache.compiled_dir', 'cache://compiled'); + $compiled_path = $grav['locator']->findResource($compiled_dir, true); + + if ($compiled_path && is_dir($compiled_path)) { + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($compiled_path, \RecursiveDirectoryIterator::SKIP_DOTS), + \RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ($iterator as $file) { + if ($file->isFile()) { + $file_age = $now - $file->getMTime(); + // Compiled files can be kept longer (60 days) + if ($file_age > ($max_age_seconds * 2)) { + @unlink($file->getPathname()); + $count++; + } + } + } + } + + return $count; + } + + /** + * Public accessor to set the enabled state of the cache + * + * @param bool|int $enabled + * @return void + */ + public function setEnabled($enabled) + { + $this->enabled = (bool)$enabled; + } + + /** + * Returns the current enabled state + * + * @return bool + */ + public function getEnabled() + { + return $this->enabled; + } + + /** + * Get cache state + * + * @return string + */ + public function getCacheStatus() + { + return 'Cache: [' . ($this->enabled ? 'true' : 'false') . '] Setting: [' . $this->driver_setting . '] Driver: [' . $this->driver_name . ']'; + } + + /** + * Automatically picks the cache mechanism to use. If you pick one manually it will use that + * If there is no config option for $driver in the config, or it's set to 'auto', it will + * pick the best option based on which cache extensions are installed. + * + * @return DoctrineCache\CacheProvider The cache driver to use + */ + public function getCacheDriver() + { + $setting = $this->driver_setting; + $driver_name = 'file'; + + // CLI compatibility requires a non-volatile cache driver + if ($this->config->get('system.cache.cli_compatibility') && ( + $setting === 'auto' || $this->isVolatileDriver($setting))) { + $setting = $driver_name; + } + + if (!$setting || $setting === 'auto') { + if (extension_loaded('apcu')) { + $driver_name = 'apcu'; + } elseif (extension_loaded('wincache')) { + $driver_name = 'wincache'; + } + } else { + $driver_name = $setting; + } + + $this->driver_name = $driver_name; + + switch ($driver_name) { + case 'apc': + case 'apcu': + $driver = new DoctrineCache\ApcuCache(); + break; + + case 'wincache': + $driver = new DoctrineCache\WinCacheCache(); + break; + + case 'memcache': + if (extension_loaded('memcache')) { + $memcache = new \Memcache(); + $memcache->connect( + $this->config->get('system.cache.memcache.server', 'localhost'), + $this->config->get('system.cache.memcache.port', 11211) + ); + $driver = new DoctrineCache\MemcacheCache(); + $driver->setMemcache($memcache); + } else { + throw new LogicException('Memcache PHP extension has not been installed'); + } + break; + + case 'memcached': + if (extension_loaded('memcached')) { + $memcached = new \Memcached(); + $memcached->addServer( + $this->config->get('system.cache.memcached.server', 'localhost'), + $this->config->get('system.cache.memcached.port', 11211) + ); + $driver = new DoctrineCache\MemcachedCache(); + $driver->setMemcached($memcached); + } else { + throw new LogicException('Memcached PHP extension has not been installed'); + } + break; + + case 'redis': + if (extension_loaded('redis')) { + $redis = new \Redis(); + $socket = $this->config->get('system.cache.redis.socket', false); + $password = $this->config->get('system.cache.redis.password', false); + $databaseId = $this->config->get('system.cache.redis.database', 0); + + if ($socket) { + $redis->connect($socket); + } else { + $redis->connect( + $this->config->get('system.cache.redis.server', 'localhost'), + $this->config->get('system.cache.redis.port', 6379) + ); + } + + // Authenticate with password if set + if ($password && !$redis->auth($password)) { + throw new \RedisException('Redis authentication failed'); + } + + // Select alternate ( !=0 ) database ID if set + if ($databaseId && !$redis->select($databaseId)) { + throw new \RedisException('Could not select alternate Redis database ID'); + } + + $driver = new DoctrineCache\RedisCache(); + $driver->setRedis($redis); + } else { + throw new LogicException('Redis PHP extension has not been installed'); + } + break; + + default: + $driver = new DoctrineCache\FilesystemCache($this->cache_dir); + break; + } + + return $driver; + } + + /** + * Gets a cached entry if it exists based on an id. If it does not exist, it returns false + * + * @param string $id the id of the cached entry + * @return mixed|bool returns the cached entry, can be any type, or false if doesn't exist + */ + public function fetch($id) + { + if ($this->enabled) { + return $this->driver->fetch($id); + } + + return false; + } + + /** + * Stores a new cached entry. + * + * @param string $id the id of the cached entry + * @param array|object|int $data the data for the cached entry to store + * @param int|null $lifetime the lifetime to store the entry in seconds + */ + public function save($id, $data, $lifetime = null) + { + if ($this->enabled) { + if ($lifetime === null) { + $lifetime = $this->getLifetime(); + } + $this->driver->save($id, $data, $lifetime); + } + } + + /** + * Deletes an item in the cache based on the id + * + * @param string $id the id of the cached data entry + * @return bool true if the item was deleted successfully + */ + public function delete($id) + { + if ($this->enabled) { + return $this->driver->delete($id); + } + + return false; + } + + /** + * Deletes all cache + * + * @return bool + */ + public function deleteAll() + { + if ($this->enabled) { + return $this->driver->deleteAll(); + } + + return false; + } + + /** + * Returns a boolean state of whether or not the item exists in the cache based on id key + * + * @param string $id the id of the cached data entry + * @return bool true if the cached items exists + */ + public function contains($id) + { + if ($this->enabled) { + return $this->driver->contains(($id)); + } + + return false; + } + + /** + * Getter method to get the cache key + * + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * Setter method to set key (Advanced) + * + * @param string $key + * @return void + */ + public function setKey($key) + { + $this->key = $key; + $this->driver->setNamespace($this->key); + } + + /** + * Helper method to clear all Grav caches + * + * @param string $remove standard|all|assets-only|images-only|cache-only + * @return array + */ + public static function clearCache($remove = 'standard') + { + $locator = Grav::instance()['locator']; + $output = []; + $user_config = USER_DIR . 'config/system.yaml'; + + switch ($remove) { + case 'all': + $remove_paths = self::$all_remove; + break; + case 'assets-only': + $remove_paths = self::$assets_remove; + break; + case 'images-only': + $remove_paths = self::$images_remove; + break; + case 'cache-only': + $remove_paths = self::$cache_remove; + break; + case 'tmp-only': + $remove_paths = self::$tmp_remove; + break; + case 'invalidate': + $remove_paths = []; + break; + default: + if (Grav::instance()['config']->get('system.cache.clear_images_by_default')) { + $remove_paths = self::$standard_remove; + } else { + $remove_paths = self::$standard_remove_no_images; + } + } + + // Delete entries in the doctrine cache if required + if (in_array($remove, ['all', 'standard'])) { + $cache = Grav::instance()['cache']; + $cache->driver->deleteAll(); + } + + // Clearing cache event to add paths to clear + Grav::instance()->fireEvent('onBeforeCacheClear', new Event(['remove' => $remove, 'paths' => &$remove_paths])); + + foreach ($remove_paths as $stream) { + // Convert stream to a real path + try { + $path = $locator->findResource($stream, true, true); + if ($path === false) { + continue; + } + + $anything = false; + $files = glob($path . '/*'); + + if (is_array($files)) { + foreach ($files as $file) { + if (is_link($file)) { + $output[] = 'Skipping symlink: ' . $file; + } elseif (is_file($file)) { + if (@unlink($file)) { + $anything = true; + } + } elseif (is_dir($file)) { + if (Folder::delete($file, false)) { + $anything = true; + } + } + } + } + + if ($anything) { + $output[] = 'Cleared: ' . $path . '/*'; + } + } catch (Exception $e) { + // stream not found or another error while deleting files. + $output[] = 'ERROR: ' . $e->getMessage(); + } + } + + $output[] = ''; + + if (($remove === 'all' || $remove === 'standard') && file_exists($user_config)) { + touch($user_config); + + $output[] = 'Touched: ' . $user_config; + $output[] = ''; + } + + // Clear stat cache + @clearstatcache(); + + // Clear opcache + if (function_exists('opcache_reset')) { + @opcache_reset(); + } + + Grav::instance()->fireEvent('onAfterCacheClear', new Event(['remove' => $remove, 'output' => &$output])); + + return $output; + } + + /** + * @return void + */ + public static function invalidateCache() + { + $user_config = USER_DIR . 'config/system.yaml'; + + if (file_exists($user_config)) { + touch($user_config); + } + + // Clear stat cache + @clearstatcache(); + + // Clear opcache + if (function_exists('opcache_reset')) { + @opcache_reset(); + } + } + + /** + * Set the cache lifetime programmatically + * + * @param int $future timestamp + * @return void + */ + public function setLifetime($future) + { + if (!$future) { + return; + } + + $interval = (int)($future - $this->now); + if ($interval > 0 && $interval < $this->getLifetime()) { + $this->lifetime = $interval; + } + } + + + /** + * Retrieve the cache lifetime (in seconds) + * + * @return int + */ + public function getLifetime() + { + if ($this->lifetime === null) { + $this->lifetime = (int)($this->config->get('system.cache.lifetime') ?: 604800); // 1 week default + } + + return $this->lifetime; + } + + /** + * Returns the current driver name + * + * @return string + */ + public function getDriverName() + { + return $this->driver_name; + } + + /** + * Returns the current driver setting + * + * @return string + */ + public function getDriverSetting() + { + return $this->driver_setting; + } + + /** + * is this driver a volatile driver in that it resides in PHP process memory + * + * @param string $setting + * @return bool + */ + public function isVolatileDriver($setting) + { + return in_array($setting, ['apc', 'apcu', 'xcache', 'wincache'], true); + } + + /** + * Static function to call as a scheduled Job to purge old Doctrine files + * + * @param bool $echo + * + * @return string|void + */ + public static function purgeJob($echo = false) + { + /** @var Cache $cache */ + $cache = Grav::instance()['cache']; + $deleted_items = $cache->purgeOldCache(); + + $max_age = $cache->config->get('system.cache.purge_max_age_days', 30); + $msg = 'Purged ' . $deleted_items . ' old cache items (files older than ' . $max_age . ' days)'; + + if ($echo) { + echo $msg; + } else { + return $msg; + } + } + + /** + * Static function to call as a scheduled Job to clear Grav cache + * + * @param string $type + * @return void + */ + public static function clearJob($type) + { + $result = static::clearCache($type); + static::invalidateCache(); + + echo strip_tags(implode("\n", $result)); + } + + /** + * @param Event $event + * @return void + */ + public function onSchedulerInitialized(Event $event) + { + /** @var Scheduler $scheduler */ + $scheduler = $event['scheduler']; + $config = Grav::instance()['config']; + + // File Cache Purge + $at = $config->get('system.cache.purge_at'); + $name = 'cache-purge'; + $logs = 'logs/' . $name . '.out'; + + $job = $scheduler->addFunction('Grav\Common\Cache::purgeJob', [true], $name); + $job->at($at); + $job->output($logs); + $job->backlink('/config/system#caching'); + + // Cache Clear + $at = $config->get('system.cache.clear_at'); + $clear_type = $config->get('system.cache.clear_job_type'); + $name = 'cache-clear'; + $logs = 'logs/' . $name . '.out'; + + $job = $scheduler->addFunction('Grav\Common\Cache::clearJob', [$clear_type], $name); + $job->at($at); + $job->output($logs); + $job->backlink('/config/system#caching'); + } +} diff --git a/system/src/Grav/Common/Composer.php b/system/src/Grav/Common/Composer.php new file mode 100644 index 0000000..f47f1c6 --- /dev/null +++ b/system/src/Grav/Common/Composer.php @@ -0,0 +1,67 @@ +path = $path ? rtrim($path, '\\/') . '/' : ''; + $this->cacheFolder = $cacheFolder; + $this->files = $files; + } + + /** + * Get filename for the compiled PHP file. + * + * @param string|null $name + * @return $this + */ + public function name($name = null) + { + if (!$this->name) { + $this->name = $name ?: md5(json_encode(array_keys($this->files))); + } + + return $this; + } + + /** + * Function gets called when cached configuration is saved. + * + * @return void + */ + public function modified() + { + } + + /** + * Get timestamp of compiled configuration + * + * @return int Timestamp of compiled configuration + */ + public function timestamp() + { + return $this->timestamp ?: time(); + } + + /** + * Load the configuration. + * + * @return mixed + */ + public function load() + { + if ($this->object) { + return $this->object; + } + + $filename = $this->createFilename(); + if (!$this->loadCompiledFile($filename) && $this->loadFiles()) { + $this->saveCompiledFile($filename); + } + + return $this->object; + } + + /** + * Returns checksum from the configuration files. + * + * You can set $this->checksum = false to disable this check. + * + * @return bool|string + */ + public function checksum() + { + if (null === $this->checksum) { + $this->checksum = md5(json_encode($this->files) . $this->version); + } + + return $this->checksum; + } + + /** + * @return string + */ + protected function createFilename() + { + return "{$this->cacheFolder}/{$this->name()->name}.php"; + } + + /** + * Create configuration object. + * + * @param array $data + * @return void + */ + abstract protected function createObject(array $data = []); + + /** + * Finalize configuration object. + * + * @return void + */ + abstract protected function finalizeObject(); + + /** + * Load single configuration file and append it to the correct position. + * + * @param string $name Name of the position. + * @param string|string[] $filename File(s) to be loaded. + * @return void + */ + abstract protected function loadFile($name, $filename); + + /** + * Load and join all configuration files. + * + * @return bool + * @internal + */ + protected function loadFiles() + { + $this->createObject(); + + $list = array_reverse($this->files); + foreach ($list as $files) { + foreach ($files as $name => $item) { + $this->loadFile($name, $this->path . $item['file']); + } + } + + $this->finalizeObject(); + + return true; + } + + /** + * Load compiled file. + * + * @param string $filename + * @return bool + * @internal + */ + protected function loadCompiledFile($filename) + { + if (!file_exists($filename)) { + return false; + } + + $cache = include $filename; + if (!is_array($cache) + || !isset($cache['checksum'], $cache['data'], $cache['@class']) + || $cache['@class'] !== get_class($this) + ) { + return false; + } + + // Load real file if cache isn't up to date (or is invalid). + if ($cache['checksum'] !== $this->checksum()) { + return false; + } + + $this->createObject($cache['data']); + $this->timestamp = $cache['timestamp'] ?? 0; + + $this->finalizeObject(); + + return true; + } + + /** + * Save compiled file. + * + * @param string $filename + * @return void + * @throws RuntimeException + * @internal + */ + protected function saveCompiledFile($filename) + { + $file = PhpFile::instance($filename); + + // Attempt to lock the file for writing. + try { + $file->lock(false); + } catch (Exception $e) { + // Another process has locked the file; we will check this in a bit. + } + + if ($file->locked() === false) { + // File was already locked by another process. + return; + } + + $cache = [ + '@class' => get_class($this), + 'timestamp' => time(), + 'checksum' => $this->checksum(), + 'files' => $this->files, + 'data' => $this->getState() + ]; + + $file->save($cache); + $file->unlock(); + $file->free(); + + $this->modified(); + } + + /** + * @return array + */ + protected function getState() + { + return $this->object->toArray(); + } +} diff --git a/system/src/Grav/Common/Config/CompiledBlueprints.php b/system/src/Grav/Common/Config/CompiledBlueprints.php new file mode 100644 index 0000000..d142b4f --- /dev/null +++ b/system/src/Grav/Common/Config/CompiledBlueprints.php @@ -0,0 +1,131 @@ +version = 2; + } + + /** + * Returns checksum from the configuration files. + * + * You can set $this->checksum = false to disable this check. + * + * @return bool|string + */ + public function checksum() + { + if (null === $this->checksum) { + $this->checksum = md5(json_encode($this->files) . json_encode($this->getTypes()) . $this->version); + } + + return $this->checksum; + } + + /** + * Create configuration object. + * + * @param array $data + */ + protected function createObject(array $data = []) + { + $this->object = (new BlueprintSchema($data))->setTypes($this->getTypes()); + } + + /** + * Get list of form field types. + * + * @return array + */ + protected function getTypes() + { + return Grav::instance()['plugins']->formFieldTypes ?: []; + } + + /** + * Finalize configuration object. + * + * @return void + */ + protected function finalizeObject() + { + } + + /** + * Load single configuration file and append it to the correct position. + * + * @param string $name Name of the position. + * @param array $files Files to be loaded. + * @return void + */ + protected function loadFile($name, $files) + { + // Load blueprint file. + $blueprint = new Blueprint($files); + + $this->object->embed($name, $blueprint->load()->toArray(), '/', true); + } + + /** + * Load and join all configuration files. + * + * @return bool + * @internal + */ + protected function loadFiles() + { + $this->createObject(); + + // Convert file list into parent list. + $list = []; + /** @var array $files */ + foreach ($this->files as $files) { + foreach ($files as $name => $item) { + $list[$name][] = $this->path . $item['file']; + } + } + + // Load files. + foreach ($list as $name => $files) { + $this->loadFile($name, $files); + } + + $this->finalizeObject(); + + return true; + } + + /** + * @return array + */ + protected function getState() + { + return $this->object->getState(); + } +} diff --git a/system/src/Grav/Common/Config/CompiledConfig.php b/system/src/Grav/Common/Config/CompiledConfig.php new file mode 100644 index 0000000..1c9af6e --- /dev/null +++ b/system/src/Grav/Common/Config/CompiledConfig.php @@ -0,0 +1,114 @@ +version = 1; + } + + /** + * Set blueprints for the configuration. + * + * @param callable $blueprints + * @return $this + */ + public function setBlueprints(callable $blueprints) + { + $this->callable = $blueprints; + + return $this; + } + + /** + * @param bool $withDefaults + * @return mixed + */ + public function load($withDefaults = false) + { + $this->withDefaults = $withDefaults; + + return parent::load(); + } + + /** + * Create configuration object. + * + * @param array $data + * @return void + */ + protected function createObject(array $data = []) + { + if ($this->withDefaults && empty($data) && is_callable($this->callable)) { + $blueprints = $this->callable; + $data = $blueprints()->getDefaults(); + } + + $this->object = new Config($data, $this->callable); + } + + /** + * Finalize configuration object. + * + * @return void + */ + protected function finalizeObject() + { + $this->object->checksum($this->checksum()); + $this->object->timestamp($this->timestamp()); + } + + /** + * Function gets called when cached configuration is saved. + * + * @return void + */ + public function modified() + { + $this->object->modified(true); + } + + /** + * Load single configuration file and append it to the correct position. + * + * @param string $name Name of the position. + * @param string $filename File to be loaded. + * @return void + */ + protected function loadFile($name, $filename) + { + $file = CompiledYamlFile::instance($filename); + $this->object->join($name, $file->content(), '/'); + $file->free(); + } +} diff --git a/system/src/Grav/Common/Config/CompiledLanguages.php b/system/src/Grav/Common/Config/CompiledLanguages.php new file mode 100644 index 0000000..e7df547 --- /dev/null +++ b/system/src/Grav/Common/Config/CompiledLanguages.php @@ -0,0 +1,83 @@ +version = 1; + } + + /** + * Create configuration object. + * + * @param array $data + * @return void + */ + protected function createObject(array $data = []) + { + $this->object = new Languages($data); + } + + /** + * Finalize configuration object. + * + * @return void + */ + protected function finalizeObject() + { + $this->object->checksum($this->checksum()); + $this->object->timestamp($this->timestamp()); + } + + + /** + * Function gets called when cached configuration is saved. + * + * @return void + */ + public function modified() + { + $this->object->modified(true); + } + + /** + * Load single configuration file and append it to the correct position. + * + * @param string $name Name of the position. + * @param string $filename File to be loaded. + * @return void + */ + protected function loadFile($name, $filename) + { + $file = CompiledYamlFile::instance($filename); + if (preg_match('|languages\.yaml$|', $filename)) { + $this->object->mergeRecursive((array) $file->content()); + } else { + $this->object->mergeRecursive([$name => $file->content()]); + } + $file->free(); + } +} diff --git a/system/src/Grav/Common/Config/Config.php b/system/src/Grav/Common/Config/Config.php new file mode 100644 index 0000000..566d7c9 --- /dev/null +++ b/system/src/Grav/Common/Config/Config.php @@ -0,0 +1,156 @@ +key) { + $this->key = md5($this->checksum . $this->timestamp); + } + + return $this->key; + } + + /** + * @param string|null $checksum + * @return string|null + */ + public function checksum($checksum = null) + { + if ($checksum !== null) { + $this->checksum = $checksum; + } + + return $this->checksum; + } + + /** + * @param bool|null $modified + * @return bool + */ + public function modified($modified = null) + { + if ($modified !== null) { + $this->modified = $modified; + } + + return $this->modified; + } + + /** + * @param int|null $timestamp + * @return int + */ + public function timestamp($timestamp = null) + { + if ($timestamp !== null) { + $this->timestamp = $timestamp; + } + + return $this->timestamp; + } + + /** + * @return $this + */ + public function reload() + { + $grav = Grav::instance(); + + // Load new configuration. + $config = ConfigServiceProvider::load($grav); + + /** @var Debugger $debugger */ + $debugger = $grav['debugger']; + + if ($config->modified()) { + // Update current configuration. + $this->items = $config->toArray(); + $this->checksum($config->checksum()); + $this->modified(true); + + $debugger->addMessage('Configuration was changed and saved.'); + } + + return $this; + } + + /** + * @return void + */ + public function debug() + { + /** @var Debugger $debugger */ + $debugger = Grav::instance()['debugger']; + + $debugger->addMessage('Environment Name: ' . $this->environment); + if ($this->modified()) { + $debugger->addMessage('Configuration reloaded and cached.'); + } + } + + /** + * @return void + */ + public function init() + { + $setup = Grav::instance()['setup']->toArray(); + foreach ($setup as $key => $value) { + if ($key === 'streams' || !is_array($value)) { + // Optimized as streams and simple values are fully defined in setup. + $this->items[$key] = $value; + } else { + $this->joinDefaults($key, $value); + } + } + + // Legacy value - Override the media.upload_limit based on PHP values + $this->items['system']['media']['upload_limit'] = Utils::getUploadLimit(); + } + + /** + * @return mixed + * @deprecated 1.5 Use Grav::instance()['languages'] instead. + */ + public function getLanguages() + { + user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use Grav::instance()[\'languages\'] instead', E_USER_DEPRECATED); + + return Grav::instance()['languages']; + } +} diff --git a/system/src/Grav/Common/Config/ConfigFileFinder.php b/system/src/Grav/Common/Config/ConfigFileFinder.php new file mode 100644 index 0000000..773022d --- /dev/null +++ b/system/src/Grav/Common/Config/ConfigFileFinder.php @@ -0,0 +1,273 @@ +base = $base ? "{$base}/" : ''; + + return $this; + } + + /** + * Return all locations for all the files with a timestamp. + * + * @param array $paths List of folders to look from. + * @param string $pattern Pattern to match the file. Pattern will also be removed from the key. + * @param int $levels Maximum number of recursive directories. + * @return array + */ + public function locateFiles(array $paths, $pattern = '|\.yaml$|', $levels = -1) + { + $list = []; + foreach ($paths as $folder) { + $list += $this->detectRecursive($folder, $pattern, $levels); + } + + return $list; + } + + /** + * Return all locations for all the files with a timestamp. + * + * @param array $paths List of folders to look from. + * @param string $pattern Pattern to match the file. Pattern will also be removed from the key. + * @param int $levels Maximum number of recursive directories. + * @return array + */ + public function getFiles(array $paths, $pattern = '|\.yaml$|', $levels = -1) + { + $list = []; + foreach ($paths as $folder) { + $path = trim(Folder::getRelativePath($folder), '/'); + + $files = $this->detectRecursive($folder, $pattern, $levels); + + $list += $files[trim($path, '/')]; + } + + return $list; + } + + /** + * Return all paths for all the files with a timestamp. + * + * @param array $paths List of folders to look from. + * @param string $pattern Pattern to match the file. Pattern will also be removed from the key. + * @param int $levels Maximum number of recursive directories. + * @return array + */ + public function listFiles(array $paths, $pattern = '|\.yaml$|', $levels = -1) + { + $list = []; + foreach ($paths as $folder) { + $list = array_merge_recursive($list, $this->detectAll($folder, $pattern, $levels)); + } + + return $list; + } + + /** + * Find filename from a list of folders. + * + * Note: Only finds the last override. + * + * @param string $filename + * @param array $folders + * @return array + */ + public function locateFileInFolder($filename, array $folders) + { + $list = []; + foreach ($folders as $folder) { + $list += $this->detectInFolder($folder, $filename); + } + + return $list; + } + + /** + * Find filename from a list of folders. + * + * @param array $folders + * @param string|null $filename + * @return array + */ + public function locateInFolders(array $folders, $filename = null) + { + $list = []; + foreach ($folders as $folder) { + $path = trim(Folder::getRelativePath($folder), '/'); + $list[$path] = $this->detectInFolder($folder, $filename); + } + + return $list; + } + + /** + * Return all existing locations for a single file with a timestamp. + * + * @param array $paths Filesystem paths to look up from. + * @param string $name Configuration file to be located. + * @param string $ext File extension (optional, defaults to .yaml). + * @return array + */ + public function locateFile(array $paths, $name, $ext = '.yaml') + { + $filename = preg_replace('|[.\/]+|', '/', $name) . $ext; + + $list = []; + foreach ($paths as $folder) { + $path = trim(Folder::getRelativePath($folder), '/'); + + if (is_file("{$folder}/{$filename}")) { + $modified = filemtime("{$folder}/{$filename}"); + } else { + $modified = 0; + } + $basename = $this->base . $name; + $list[$path] = [$basename => ['file' => "{$path}/{$filename}", 'modified' => $modified]]; + } + + return $list; + } + + /** + * Detects all directories with a configuration file and returns them with last modification time. + * + * @param string $folder Location to look up from. + * @param string $pattern Pattern to match the file. Pattern will also be removed from the key. + * @param int $levels Maximum number of recursive directories. + * @return array + * @internal + */ + protected function detectRecursive($folder, $pattern, $levels) + { + $path = trim(Folder::getRelativePath($folder), '/'); + + if (is_dir($folder)) { + // Find all system and user configuration files. + $options = [ + 'levels' => $levels, + 'compare' => 'Filename', + 'pattern' => $pattern, + 'filters' => [ + 'pre-key' => $this->base, + 'key' => $pattern, + 'value' => function (RecursiveDirectoryIterator $file) use ($path) { + return ['file' => "{$path}/{$file->getSubPathname()}", 'modified' => $file->getMTime()]; + } + ], + 'key' => 'SubPathname' + ]; + + $list = Folder::all($folder, $options); + + ksort($list); + } else { + $list = []; + } + + return [$path => $list]; + } + + /** + * Detects all directories with the lookup file and returns them with last modification time. + * + * @param string $folder Location to look up from. + * @param string|null $lookup Filename to be located (defaults to directory name). + * @return array + * @internal + */ + protected function detectInFolder($folder, $lookup = null) + { + $folder = rtrim($folder, '/'); + $path = trim(Folder::getRelativePath($folder), '/'); + $base = $path === $folder ? '' : ($path ? substr($folder, 0, -strlen($path)) : $folder . '/'); + + $list = []; + + if (is_dir($folder)) { + $iterator = new DirectoryIterator($folder); + foreach ($iterator as $directory) { + if (!$directory->isDir() || $directory->isDot()) { + continue; + } + + $name = $directory->getFilename(); + $find = ($lookup ?: $name) . '.yaml'; + $filename = "{$path}/{$name}/{$find}"; + + if (file_exists($base . $filename)) { + $basename = $this->base . $name; + $list[$basename] = ['file' => $filename, 'modified' => filemtime($base . $filename)]; + } + } + } + + return $list; + } + + /** + * Detects all plugins with a configuration file and returns them with last modification time. + * + * @param string $folder Location to look up from. + * @param string $pattern Pattern to match the file. Pattern will also be removed from the key. + * @param int $levels Maximum number of recursive directories. + * @return array + * @internal + */ + protected function detectAll($folder, $pattern, $levels) + { + $path = trim(Folder::getRelativePath($folder), '/'); + + if (is_dir($folder)) { + // Find all system and user configuration files. + $options = [ + 'levels' => $levels, + 'compare' => 'Filename', + 'pattern' => $pattern, + 'filters' => [ + 'pre-key' => $this->base, + 'key' => $pattern, + 'value' => function (RecursiveDirectoryIterator $file) use ($path) { + return ["{$path}/{$file->getSubPathname()}" => $file->getMTime()]; + } + ], + 'key' => 'SubPathname' + ]; + + $list = Folder::all($folder, $options); + + ksort($list); + } else { + $list = []; + } + + return $list; + } +} diff --git a/system/src/Grav/Common/Config/Languages.php b/system/src/Grav/Common/Config/Languages.php new file mode 100644 index 0000000..ac9ede6 --- /dev/null +++ b/system/src/Grav/Common/Config/Languages.php @@ -0,0 +1,107 @@ +checksum = $checksum; + } + + return $this->checksum; + } + + /** + * @param bool|null $modified + * @return bool + */ + public function modified($modified = null) + { + if ($modified !== null) { + $this->modified = $modified; + } + + return $this->modified; + } + + /** + * @param int|null $timestamp + * @return int + */ + public function timestamp($timestamp = null) + { + if ($timestamp !== null) { + $this->timestamp = $timestamp; + } + + return $this->timestamp; + } + + /** + * @return void + */ + public function reformat() + { + if (isset($this->items['plugins'])) { + $this->items = array_merge_recursive($this->items, $this->items['plugins']); + unset($this->items['plugins']); + } + } + + /** + * @param array $data + * @return void + */ + public function mergeRecursive(array $data) + { + $this->items = Utils::arrayMergeRecursiveUnique($this->items, $data); + } + + /** + * @param string $lang + * @return array + */ + public function flattenByLang($lang) + { + $language = $this->items[$lang]; + return Utils::arrayFlattenDotNotation($language); + } + + /** + * @param array $array + * @return array + */ + public function unflatten($array) + { + return Utils::arrayUnflattenDotNotation($array); + } +} diff --git a/system/src/Grav/Common/Config/Setup.php b/system/src/Grav/Common/Config/Setup.php new file mode 100644 index 0000000..62995d4 --- /dev/null +++ b/system/src/Grav/Common/Config/Setup.php @@ -0,0 +1,423 @@ + 'unknown', + '127.0.0.1' => 'localhost', + '::1' => 'localhost' + ]; + + /** + * @var string|null Current environment normalized to lower case. + */ + public static $environment; + + /** @var string */ + public static $securityFile = 'config://security.yaml'; + + /** @var array */ + protected $streams = [ + 'user' => [ + 'type' => 'ReadOnlyStream', + 'force' => true, + 'prefixes' => [ + '' => [] // Set in constructor + ] + ], + 'cache' => [ + 'type' => 'Stream', + 'force' => true, + 'prefixes' => [ + '' => [], // Set in constructor + 'images' => ['images'] + ] + ], + 'log' => [ + 'type' => 'Stream', + 'force' => true, + 'prefixes' => [ + '' => [] // Set in constructor + ] + ], + 'tmp' => [ + 'type' => 'Stream', + 'force' => true, + 'prefixes' => [ + '' => [] // Set in constructor + ] + ], + 'backup' => [ + 'type' => 'Stream', + 'force' => true, + 'prefixes' => [ + '' => [] // Set in constructor + ] + ], + 'environment' => [ + 'type' => 'ReadOnlyStream' + // If not defined, environment will be set up in the constructor. + ], + 'system' => [ + 'type' => 'ReadOnlyStream', + 'prefixes' => [ + '' => ['system'], + ] + ], + 'asset' => [ + 'type' => 'Stream', + 'prefixes' => [ + '' => ['assets'], + ] + ], + 'blueprints' => [ + 'type' => 'ReadOnlyStream', + 'prefixes' => [ + '' => ['environment://blueprints', 'user://blueprints', 'system://blueprints'], + ] + ], + 'config' => [ + 'type' => 'ReadOnlyStream', + 'prefixes' => [ + '' => ['environment://config', 'user://config', 'system://config'], + ] + ], + 'plugins' => [ + 'type' => 'ReadOnlyStream', + 'prefixes' => [ + '' => ['user://plugins'], + ] + ], + 'plugin' => [ + 'type' => 'ReadOnlyStream', + 'prefixes' => [ + '' => ['user://plugins'], + ] + ], + 'themes' => [ + 'type' => 'ReadOnlyStream', + 'prefixes' => [ + '' => ['user://themes'], + ] + ], + 'languages' => [ + 'type' => 'ReadOnlyStream', + 'prefixes' => [ + '' => ['environment://languages', 'user://languages', 'system://languages'], + ] + ], + 'image' => [ + 'type' => 'Stream', + 'prefixes' => [ + '' => ['user://images', 'system://images'] + ] + ], + 'page' => [ + 'type' => 'ReadOnlyStream', + 'prefixes' => [ + '' => ['user://pages'] + ] + ], + 'user-data' => [ + 'type' => 'Stream', + 'force' => true, + 'prefixes' => [ + '' => ['user://data'] + ] + ], + 'account' => [ + 'type' => 'ReadOnlyStream', + 'prefixes' => [ + '' => ['user://accounts'] + ] + ], + ]; + + /** + * @param Container|array $container + */ + public function __construct($container) + { + // Configure main streams. + $abs = str_starts_with(GRAV_SYSTEM_PATH, '/'); + $this->streams['system']['prefixes'][''] = $abs ? ['system', GRAV_SYSTEM_PATH] : ['system']; + $this->streams['user']['prefixes'][''] = [GRAV_USER_PATH]; + $this->streams['cache']['prefixes'][''] = [GRAV_CACHE_PATH]; + $this->streams['log']['prefixes'][''] = [GRAV_LOG_PATH]; + $this->streams['tmp']['prefixes'][''] = [GRAV_TMP_PATH]; + $this->streams['backup']['prefixes'][''] = [GRAV_BACKUP_PATH]; + + // If environment is not set, look for the environment variable and then the constant. + $environment = static::$environment ?? + (defined('GRAV_ENVIRONMENT') ? GRAV_ENVIRONMENT : (getenv('GRAV_ENVIRONMENT') ?: null)); + + // If no environment is set, make sure we get one (CLI or hostname). + if (null === $environment) { + if (defined('GRAV_CLI')) { + $request = null; + $uri = null; + $environment = 'cli'; + } else { + /** @var ServerRequestInterface $request */ + $request = $container['request']; + $uri = $request->getUri(); + $environment = $uri->getHost(); + } + } + + // Resolve server aliases to the proper environment. + static::$environment = static::$environments[$environment] ?? $environment; + + // Pre-load setup.php which contains our initial configuration. + // Configuration may contain dynamic parts, which is why we need to always load it. + // If GRAV_SETUP_PATH has been defined, use it, otherwise use defaults. + $setupFile = defined('GRAV_SETUP_PATH') ? GRAV_SETUP_PATH : (getenv('GRAV_SETUP_PATH') ?: null); + if (null !== $setupFile) { + // Make sure that the custom setup file exists. Terminates the script if not. + if (!str_starts_with($setupFile, '/')) { + $setupFile = GRAV_WEBROOT . '/' . $setupFile; + } + if (!is_file($setupFile)) { + echo 'GRAV_SETUP_PATH is defined but does not point to existing setup file.'; + exit(1); + } + } else { + $setupFile = GRAV_WEBROOT . '/setup.php'; + if (!is_file($setupFile)) { + $setupFile = GRAV_WEBROOT . '/' . GRAV_USER_PATH . '/setup.php'; + } + if (!is_file($setupFile)) { + $setupFile = null; + } + } + $setup = $setupFile ? (array) include $setupFile : []; + + // Add default streams defined in beginning of the class. + if (!isset($setup['streams']['schemes'])) { + $setup['streams']['schemes'] = []; + } + $setup['streams']['schemes'] += $this->streams; + + // Initialize class. + parent::__construct($setup); + + $this->def('environment', static::$environment); + + // Figure out path for the current environment. + $envPath = defined('GRAV_ENVIRONMENT_PATH') ? GRAV_ENVIRONMENT_PATH : (getenv('GRAV_ENVIRONMENT_PATH') ?: null); + if (null === $envPath) { + // Find common path for all environments and append current environment into it. + $envPath = defined('GRAV_ENVIRONMENTS_PATH') ? GRAV_ENVIRONMENTS_PATH : (getenv('GRAV_ENVIRONMENTS_PATH') ?: null); + if (null !== $envPath) { + $envPath .= '/'; + } else { + // Use default location. Start with Grav 1.7 default. + $envPath = GRAV_WEBROOT. '/' . GRAV_USER_PATH . '/env'; + if (is_dir($envPath)) { + $envPath = 'user://env/'; + } else { + // Fallback to Grav 1.6 default. + $envPath = 'user://'; + } + } + $envPath .= $this->get('environment'); + } + + // Set up environment. + $this->def('environment', static::$environment); + $this->def('streams.schemes.environment.prefixes', ['' => [$envPath]]); + } + + /** + * @return $this + * @throws RuntimeException + * @throws InvalidArgumentException + */ + public function init() + { + $locator = new UniformResourceLocator(GRAV_WEBROOT); + $files = []; + + $guard = 5; + do { + $check = $files; + $this->initializeLocator($locator); + $files = $locator->findResources('config://streams.yaml'); + + if ($check === $files) { + break; + } + + // Update streams. + foreach (array_reverse($files) as $path) { + $file = CompiledYamlFile::instance($path); + $content = (array)$file->content(); + if (!empty($content['schemes'])) { + $this->items['streams']['schemes'] = $content['schemes'] + $this->items['streams']['schemes']; + } + } + } while (--$guard); + + if (!$guard) { + throw new RuntimeException('Setup: Configuration reload loop detected!'); + } + + // Make sure we have valid setup. + $this->check($locator); + + return $this; + } + + /** + * Initialize resource locator by using the configuration. + * + * @param UniformResourceLocator $locator + * @return void + * @throws BadMethodCallException + */ + public function initializeLocator(UniformResourceLocator $locator) + { + $locator->reset(); + + $schemes = (array) $this->get('streams.schemes', []); + + foreach ($schemes as $scheme => $config) { + if (isset($config['paths'])) { + $locator->addPath($scheme, '', $config['paths']); + } + + $override = $config['override'] ?? false; + $force = $config['force'] ?? false; + + if (isset($config['prefixes'])) { + foreach ((array)$config['prefixes'] as $prefix => $paths) { + $locator->addPath($scheme, $prefix, $paths, $override, $force); + } + } + } + } + + /** + * Get available streams and their types from the configuration. + * + * @return array + */ + public function getStreams() + { + $schemes = []; + foreach ((array) $this->get('streams.schemes') as $scheme => $config) { + $type = $config['type'] ?? 'ReadOnlyStream'; + if ($type[0] !== '\\') { + $type = '\\RocketTheme\\Toolbox\\StreamWrapper\\' . $type; + } + + $schemes[$scheme] = $type; + } + + return $schemes; + } + + /** + * @param UniformResourceLocator $locator + * @return void + * @throws InvalidArgumentException + * @throws BadMethodCallException + * @throws RuntimeException + */ + protected function check(UniformResourceLocator $locator) + { + $streams = $this->items['streams']['schemes'] ?? null; + if (!is_array($streams)) { + throw new InvalidArgumentException('Configuration is missing streams.schemes!'); + } + $diff = array_keys(array_diff_key($this->streams, $streams)); + if ($diff) { + throw new InvalidArgumentException( + sprintf('Configuration is missing keys %s from streams.schemes!', implode(', ', $diff)) + ); + } + + try { + // If environment is found, remove all missing override locations (B/C compatibility). + if ($locator->findResource('environment://', true)) { + $force = $this->get('streams.schemes.environment.force', false); + if (!$force) { + $prefixes = $this->get('streams.schemes.environment.prefixes.'); + $update = false; + foreach ($prefixes as $i => $prefix) { + if ($locator->isStream($prefix)) { + if ($locator->findResource($prefix, true)) { + break; + } + } elseif (file_exists($prefix)) { + break; + } + + unset($prefixes[$i]); + $update = true; + } + + if ($update) { + $this->set('streams.schemes.environment.prefixes', ['' => array_values($prefixes)]); + $this->initializeLocator($locator); + } + } + } + + if (!$locator->findResource('environment://config', true)) { + // If environment does not have its own directory, remove it from the lookup. + $prefixes = $this->get('streams.schemes.environment.prefixes'); + $prefixes['config'] = []; + + $this->set('streams.schemes.environment.prefixes', $prefixes); + $this->initializeLocator($locator); + } + + // Create security.yaml salt if it doesn't exist into existing configuration environment if possible. + $securityFile = Utils::basename(static::$securityFile); + $securityFolder = substr(static::$securityFile, 0, -\strlen($securityFile)); + $securityFolder = $locator->findResource($securityFolder, true) ?: $locator->findResource($securityFolder, true, true); + $filename = "{$securityFolder}/{$securityFile}"; + + $security_file = CompiledYamlFile::instance($filename); + $security_content = (array)$security_file->content(); + + if (!isset($security_content['salt'])) { + $security_content = array_merge($security_content, ['salt' => Utils::generateRandomString(14)]); + $security_file->content($security_content); + $security_file->save(); + $security_file->free(); + } + } catch (RuntimeException $e) { + throw new RuntimeException(sprintf('Grav failed to initialize: %s', $e->getMessage()), 500, $e); + } + } +} diff --git a/system/src/Grav/Common/Data/Blueprint.php b/system/src/Grav/Common/Data/Blueprint.php new file mode 100644 index 0000000..296dc54 --- /dev/null +++ b/system/src/Grav/Common/Data/Blueprint.php @@ -0,0 +1,594 @@ +blueprintSchema) { + $this->blueprintSchema = clone $this->blueprintSchema; + } + } + + /** + * @param string $scope + * @return void + */ + public function setScope($scope) + { + $this->scope = $scope; + } + + /** + * @param object $object + * @return void + */ + public function setObject($object) + { + $this->object = $object; + } + + /** + * Set default values for field types. + * + * @param array $types + * @return $this + */ + public function setTypes(array $types) + { + $this->initInternals(); + + $this->blueprintSchema->setTypes($types); + + return $this; + } + + /** + * @param string $name + * @return array|mixed|null + * @since 1.7 + */ + public function getDefaultValue(string $name) + { + $path = explode('.', $name); + $current = $this->getDefaults(); + + 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 null; + } + } + + return $current; + } + + /** + * Get nested structure containing default values defined in the blueprints. + * + * Fields without default value are ignored in the list. + * + * @return array + */ + public function getDefaults() + { + $this->initInternals(); + + if (null === $this->defaults) { + $this->defaults = $this->blueprintSchema->getDefaults(); + } + + return $this->defaults; + } + + /** + * Initialize blueprints with its dynamic fields. + * + * @return $this + */ + public function init() + { + foreach ($this->dynamic as $key => $data) { + // Locate field. + $path = explode('/', $key); + $current = &$this->items; + + foreach ($path as $field) { + if (is_object($current)) { + // Handle objects. + if (!isset($current->{$field})) { + $current->{$field} = []; + } + + $current = &$current->{$field}; + } else { + // Handle arrays and scalars. + if (!is_array($current)) { + $current = [$field => []]; + } elseif (!isset($current[$field])) { + $current[$field] = []; + } + + $current = &$current[$field]; + } + } + + // Set dynamic property. + foreach ($data as $property => $call) { + $action = $call['action']; + $method = 'dynamic' . ucfirst($action); + $call['object'] = $this->object; + + if (isset($this->handlers[$action])) { + $callable = $this->handlers[$action]; + $callable($current, $property, $call); + } elseif (method_exists($this, $method)) { + $this->{$method}($current, $property, $call); + } + } + } + + return $this; + } + + /** + * Extend blueprint with another blueprint. + * + * @param BlueprintForm|array $extends + * @param bool $append + * @return $this + */ + public function extend($extends, $append = false) + { + parent::extend($extends, $append); + + $this->deepInit($this->items); + + return $this; + } + + /** + * @param string $name + * @param mixed $value + * @param string $separator + * @param bool $append + * @return $this + */ + public function embed($name, $value, $separator = '/', $append = false) + { + parent::embed($name, $value, $separator, $append); + + $this->deepInit($this->items); + + return $this; + } + + /** + * Merge two arrays by using blueprints. + * + * @param array $data1 + * @param array $data2 + * @param string|null $name Optional + * @param string $separator Optional + * @return array + */ + public function mergeData(array $data1, array $data2, $name = null, $separator = '.') + { + $this->initInternals(); + + return $this->blueprintSchema->mergeData($data1, $data2, $name, $separator); + } + + /** + * Process data coming from a form. + * + * @param array $data + * @param array $toggles + * @return array + */ + public function processForm(array $data, array $toggles = []) + { + $this->initInternals(); + + return $this->blueprintSchema->processForm($data, $toggles); + } + + /** + * Return data fields that do not exist in blueprints. + * + * @param array $data + * @param string $prefix + * @return array + */ + public function extra(array $data, $prefix = '') + { + $this->initInternals(); + + return $this->blueprintSchema->extra($data, $prefix); + } + + /** + * Validate data against blueprints. + * + * @param array $data + * @param array $options + * @return void + * @throws RuntimeException + */ + public function validate(array $data, array $options = []) + { + $this->initInternals(); + + $this->blueprintSchema->validate($data, $options); + } + + /** + * Filter data by using blueprints. + * + * @param array $data + * @param bool $missingValuesAsNull + * @param bool $keepEmptyValues + * @return array + */ + public function filter(array $data, bool $missingValuesAsNull = false, bool $keepEmptyValues = false) + { + $this->initInternals(); + + return $this->blueprintSchema->filter($data, $missingValuesAsNull, $keepEmptyValues) ?? []; + } + + + /** + * Flatten data by using blueprints. + * + * @param array $data Data to be flattened. + * @param bool $includeAll True if undefined properties should also be included. + * @param string $name Property which will be flattened, useful for flattening repeating data. + * @return array + */ + public function flattenData(array $data, bool $includeAll = false, string $name = '') + { + $this->initInternals(); + + return $this->blueprintSchema->flattenData($data, $includeAll, $name); + } + + + /** + * Return blueprint data schema. + * + * @return BlueprintSchema + */ + public function schema() + { + $this->initInternals(); + + return $this->blueprintSchema; + } + + /** + * @param string $name + * @param callable $callable + * @return void + */ + public function addDynamicHandler(string $name, callable $callable): void + { + $this->handlers[$name] = $callable; + } + + /** + * Initialize validator. + * + * @return void + */ + protected function initInternals() + { + if (null === $this->blueprintSchema) { + $types = Grav::instance()['plugins']->formFieldTypes; + + $this->blueprintSchema = new BlueprintSchema; + + if ($types) { + $this->blueprintSchema->setTypes($types); + } + + $this->blueprintSchema->embed('', $this->items); + $this->blueprintSchema->init(); + $this->defaults = null; + } + } + + /** + * @param string $filename + * @return array + */ + protected function loadFile($filename) + { + $file = CompiledYamlFile::instance($filename); + $content = (array)$file->content(); + $file->free(); + + return $content; + } + + /** + * @param string|array $path + * @param string|null $context + * @return array + */ + protected function getFiles($path, $context = null) + { + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + + if (is_string($path) && !$locator->isStream($path)) { + if (is_file($path)) { + return [$path]; + } + + // Find path overrides. + if (null === $context) { + $paths = (array) ($this->overrides[$path] ?? null); + } else { + $paths = []; + } + + // Add path pointing to default context. + if ($context === null) { + $context = $this->context; + } + + if ($context && $context[strlen($context)-1] !== '/') { + $context .= '/'; + } + + $path = $context . $path; + + if (!preg_match('/\.yaml$/', $path)) { + $path .= '.yaml'; + } + + $paths[] = $path; + } else { + $paths = (array) $path; + } + + $files = []; + foreach ($paths as $lookup) { + if (is_string($lookup) && strpos($lookup, '://')) { + $files = array_merge($files, $locator->findResources($lookup)); + } else { + $files[] = $lookup; + } + } + + return array_values(array_unique($files)); + } + + /** + * @param array $field + * @param string $property + * @param array $call + * @return void + */ + protected function dynamicData(array &$field, $property, array &$call) + { + $params = $call['params']; + + if (is_array($params)) { + $function = array_shift($params); + } else { + $function = $params; + $params = []; + } + + [$o, $f] = explode('::', $function, 2); + + $data = null; + if (!$f) { + if (function_exists($o)) { + $data = call_user_func_array($o, $params); + } + } else { + if (method_exists($o, $f)) { + $data = call_user_func_array([$o, $f], $params); + } + } + + // If function returns a value, + if (null !== $data) { + if (is_array($data) && isset($field[$property]) && is_array($field[$property])) { + // Combine field and @data-field together. + $field[$property] += $data; + } else { + // Or create/replace field with @data-field. + $field[$property] = $data; + } + } + } + + /** + * @param array $field + * @param string $property + * @param array $call + * @return void + */ + protected function dynamicConfig(array &$field, $property, array &$call) + { + $params = $call['params']; + if (is_array($params)) { + $value = array_shift($params); + $params = array_shift($params); + } else { + $value = $params; + $params = []; + } + + $default = $field[$property] ?? null; + $config = Grav::instance()['config']->get($value, $default); + if (!empty($field['value_only'])) { + $config = array_combine($config, $config); + } + + if (null !== $config) { + if (!empty($params['append']) && is_array($config) && isset($field[$property]) && is_array($field[$property])) { + // Combine field and @config-field together. + $field[$property] += $config; + } else { + // Or create/replace field with @config-field. + $field[$property] = $config; + } + } + } + + /** + * @param array $field + * @param string $property + * @param array $call + * @return void + */ + protected function dynamicSecurity(array &$field, $property, array &$call) + { + if ($property || !empty($field['validate']['ignore'])) { + return; + } + + $grav = Grav::instance(); + $actions = (array)$call['params']; + + /** @var UserInterface|null $user */ + $user = $grav['user'] ?? null; + $success = null !== $user; + if ($success) { + $success = $this->resolveActions($user, $actions); + } + if (!$success) { + static::addPropertyRecursive($field, 'validate', ['ignore' => true]); + } + } + + /** + * @param UserInterface|null $user + * @param array $actions + * @param string $op + * @return bool + */ + protected function resolveActions(?UserInterface $user, array $actions, string $op = 'and') + { + if (null === $user) { + return false; + } + + $c = $i = count($actions); + foreach ($actions as $key => $action) { + if (!is_int($key) && is_array($actions)) { + $i -= $this->resolveActions($user, $action, $key); + } elseif ($user->authorize($action)) { + $i--; + } + } + + if ($op === 'and') { + return $i === 0; + } + + return $c !== $i; + } + + /** + * @param array $field + * @param string $property + * @param array $call + * @return void + */ + protected function dynamicScope(array &$field, $property, array &$call) + { + if ($property && $property !== 'ignore') { + return; + } + + $scopes = (array)$call['params']; + $matches = in_array($this->scope, $scopes, true); + if ($this->scope && $property !== 'ignore') { + $matches = !$matches; + } + + if ($matches) { + static::addPropertyRecursive($field, 'validate', ['ignore' => true]); + return; + } + } + + /** + * @param array $field + * @param string $property + * @param mixed $value + * @return void + */ + public static function addPropertyRecursive(array &$field, $property, $value) + { + if (is_array($value) && isset($field[$property]) && is_array($field[$property])) { + $field[$property] = array_merge_recursive($field[$property], $value); + } else { + $field[$property] = $value; + } + + if (!empty($field['fields'])) { + foreach ($field['fields'] as $key => &$child) { + static::addPropertyRecursive($child, $property, $value); + } + } + } +} diff --git a/system/src/Grav/Common/Data/BlueprintSchema.php b/system/src/Grav/Common/Data/BlueprintSchema.php new file mode 100644 index 0000000..8db6654 --- /dev/null +++ b/system/src/Grav/Common/Data/BlueprintSchema.php @@ -0,0 +1,461 @@ + true, 'xss_check' => true]; + + /** @var array */ + protected $ignoreFormKeys = [ + 'title' => true, + 'help' => true, + 'placeholder' => true, + 'placeholder_key' => true, + 'placeholder_value' => true, + 'fields' => true + ]; + + /** + * @return array + */ + public function getTypes() + { + return $this->types; + } + + /** + * @param string $name + * @return array + */ + public function getType($name) + { + return $this->types[$name] ?? []; + } + + /** + * @param string $name + * @return array|null + */ + public function getNestedRules(string $name) + { + return $this->getNested($name); + } + + /** + * Validate data against blueprints. + * + * @param array $data + * @param array $options + * @return void + * @throws RuntimeException + */ + public function validate(array $data, array $options = []) + { + try { + $validation = $this->items['']['form']['validation'] ?? 'loose'; + $messages = $this->validateArray($data, $this->nested, $validation === 'strict', $options['xss_check'] ?? true); + } catch (RuntimeException $e) { + throw (new ValidationException($e->getMessage(), $e->getCode(), $e))->setMessages(); + } + + if (!empty($messages)) { + throw (new ValidationException('', 400))->setMessages($messages); + } + } + + /** + * @param array $data + * @param array $toggles + * @return array + */ + public function processForm(array $data, array $toggles = []) + { + return $this->processFormRecursive($data, $toggles, $this->nested) ?? []; + } + + /** + * Filter data by using blueprints. + * + * @param array $data Incoming data, for example from a form. + * @param bool $missingValuesAsNull Include missing values as nulls. + * @param bool $keepEmptyValues Include empty values. + * @return array + */ + public function filter(array $data, $missingValuesAsNull = false, $keepEmptyValues = false) + { + $this->buildIgnoreNested($this->nested); + + return $this->filterArray($data, $this->nested, '', $missingValuesAsNull, $keepEmptyValues) ?? []; + } + + /** + * Flatten data by using blueprints. + * + * @param array $data Data to be flattened. + * @param bool $includeAll True if undefined properties should also be included. + * @param string $name Property which will be flattened, useful for flattening repeating data. + * @return array + */ + public function flattenData(array $data, bool $includeAll = false, string $name = '') + { + $prefix = $name !== '' ? $name . '.' : ''; + + $list = []; + if ($includeAll) { + $items = $name !== '' ? $this->getProperty($name)['fields'] ?? [] : $this->items; + foreach ($items as $key => $rules) { + $type = $rules['type'] ?? ''; + $ignore = (bool) array_filter((array)($rules['validate']['ignore'] ?? [])) ?? false; + if (!str_starts_with($type, '_') && !str_contains($key, '*') && $ignore !== true) { + $list[$prefix . $key] = null; + } + } + } + + $nested = $this->getNestedRules($name); + + return array_replace($list, $this->flattenArray($data, $nested, $prefix)); + } + + /** + * @param array $data + * @param array $rules + * @param string $prefix + * @return array + */ + protected function flattenArray(array $data, array $rules, string $prefix) + { + $array = []; + + foreach ($data as $key => $field) { + $val = $rules[$key] ?? $rules['*'] ?? null; + $rule = is_string($val) ? $this->items[$val] : null; + + if ($rule || isset($val['*'])) { + // Item has been defined in blueprints. + $array[$prefix.$key] = $field; + } elseif (is_array($field) && is_array($val)) { + // Array has been defined in blueprints. + $array += $this->flattenArray($field, $val, $prefix . $key . '.'); + } else { + // Undefined/extra item. + $array[$prefix.$key] = $field; + } + } + + return $array; + } + + /** + * @param array $data + * @param array $rules + * @param bool $strict + * @param bool $xss + * @return array + * @throws RuntimeException + */ + protected function validateArray(array $data, array $rules, bool $strict, bool $xss = true) + { + $messages = $this->checkRequired($data, $rules); + + foreach ($data as $key => $child) { + $val = $rules[$key] ?? $rules['*'] ?? null; + $rule = is_string($val) ? $this->items[$val] : null; + $checkXss = $xss; + + if ($rule) { + // Item has been defined in blueprints. + if (!empty($rule['disabled']) || !empty($rule['validate']['ignore'])) { + // Skip validation in the ignored field. + continue; + } + + $messages += Validation::validate($child, $rule); + + if (isset($rule['validate']['match']) || isset($rule['validate']['match_exact']) || isset($rule['validate']['match_any'])) { + $ruleKey = current(array_intersect(['match', 'match_exact', 'match_any'], array_keys($rule['validate']))); + $otherKey = $rule['validate'][$ruleKey] ?? null; + $otherVal = $data[$otherKey] ?? null; + $otherLabel = $this->items[$otherKey]['label'] ?? $otherKey; + $currentVal = $data[$key] ?? null; + $currentLabel = $this->items[$key]['label'] ?? $key; + + // Determine comparison type (loose, strict, substring) + // Perform comparison: + $isValid = false; + if ($ruleKey === 'match') { + $isValid = ($currentVal == $otherVal); + } elseif ($ruleKey === 'match_exact') { + $isValid = ($currentVal === $otherVal); + } elseif ($ruleKey === 'match_any') { + // If strings: + if (is_string($currentVal) && is_string($otherVal)) { + $isValid = (strlen($currentVal) && strlen($otherVal) && (str_contains($currentVal, + $otherVal) || strpos($otherVal, $currentVal) !== false)); + } + // If arrays: + if (is_array($currentVal) && is_array($otherVal)) { + $common = array_intersect($currentVal, $otherVal); + $isValid = !empty($common); + } + } + if (!$isValid) { + $messages[$rule['name']][] = sprintf(Grav::instance()['language']->translate('PLUGIN_FORM.VALIDATION_MATCH'), $currentLabel, $otherLabel); + } + } + + } elseif (is_array($child) && is_array($val)) { + // Array has been defined in blueprints. + $messages += $this->validateArray($child, $val, $strict); + $checkXss = false; + + } elseif ($strict) { + // Undefined/extra item in strict mode. + /** @var Config $config */ + $config = Grav::instance()['config']; + if (!$config->get('system.strict_mode.blueprint_strict_compat', true)) { + throw new RuntimeException(sprintf('%s is not defined in blueprints', $key), 400); + } + + user_error(sprintf('Having extra key %s in your data is deprecated with blueprint having \'validation: strict\'', $key), E_USER_DEPRECATED); + } + + if ($checkXss) { + $messages += Validation::checkSafety($child, $rule ?: ['name' => $key]); + } + } + + return $messages; + } + + /** + * @param array $data + * @param array $rules + * @param string $parent + * @param bool $missingValuesAsNull + * @param bool $keepEmptyValues + * @return array|null + */ + protected function filterArray(array $data, array $rules, string $parent, bool $missingValuesAsNull, bool $keepEmptyValues) + { + $results = []; + + foreach ($data as $key => $field) { + $val = $rules[$key] ?? $rules['*'] ?? null; + $rule = is_string($val) ? $this->items[$val] : $this->items[$parent . $key] ?? null; + + if (!empty($rule['disabled']) || !empty($rule['validate']['ignore'])) { + // Skip any data in the ignored field. + unset($results[$key]); + continue; + } + + if (null === $field) { + if ($missingValuesAsNull) { + $results[$key] = null; + } else { + unset($results[$key]); + } + continue; + } + + $isParent = isset($val['*']); + $type = $rule['type'] ?? null; + + if (!$isParent && $type && $type !== '_parent') { + $field = Validation::filter($field, $rule); + } elseif (is_array($field) && is_array($val)) { + // Array has been defined in blueprints. + $k = $isParent ? '*' : $key; + $field = $this->filterArray($field, $val, $parent . $k . '.', $missingValuesAsNull, $keepEmptyValues); + + if (null === $field) { + // Nested parent has no values. + unset($results[$key]); + continue; + } + } elseif (isset($rules['validation']) && $rules['validation'] === 'strict') { + // Skip any extra data. + continue; + } + + if ($keepEmptyValues || (null !== $field && (!is_array($field) || !empty($field)))) { + $results[$key] = $field; + } + } + + return $results ?: null; + } + + /** + * @param array $nested + * @param string $parent + * @return bool + */ + protected function buildIgnoreNested(array $nested, $parent = '') + { + $ignore = true; + foreach ($nested as $key => $val) { + $key = $parent . $key; + if (is_array($val)) { + $ignore = $this->buildIgnoreNested($val, $key . '.') && $ignore; // Keep the order! + } else { + $child = $this->items[$key] ?? null; + $ignore = $ignore && (!$child || !empty($child['disabled']) || !empty($child['validate']['ignore'])); + } + } + if ($ignore) { + $key = trim($parent, '.'); + $this->items[$key]['validate']['ignore'] = true; + } + + return $ignore; + } + + /** + * @param array|null $data + * @param array $toggles + * @param array $nested + * @return array|null + */ + protected function processFormRecursive(?array $data, array $toggles, array $nested) + { + foreach ($nested as $key => $value) { + if ($key === '') { + continue; + } + if ($key === '*') { + // TODO: Add support to collections. + continue; + } + if (is_array($value)) { + // Special toggle handling for all the nested data. + $toggle = $toggles[$key] ?? []; + if (!is_array($toggle)) { + if (!$toggle) { + $data[$key] = null; + + continue; + } + + $toggle = []; + } + // Recursively fetch the items. + $childData = $data[$key] ?? null; + if (null !== $childData && !is_array($childData)) { + throw new \RuntimeException(sprintf("Bad form data for field collection '%s': %s used instead of an array", $key, gettype($childData))); + } + $data[$key] = $this->processFormRecursive($data[$key] ?? null, $toggle, $value); + } else { + $field = $this->get($value); + // Do not add the field if: + if ( + // Not an input field + !$field + // Field has been disabled + || !empty($field['disabled']) + // Field validation is set to be ignored + || !empty($field['validate']['ignore']) + // Field is overridable and the toggle is turned off + || (!empty($field['overridable']) && empty($toggles[$key])) + ) { + continue; + } + if (!isset($data[$key])) { + $data[$key] = null; + } + } + } + + return $data; + } + + /** + * @param array $data + * @param array $fields + * @return array + */ + protected function checkRequired(array $data, array $fields) + { + $messages = []; + + foreach ($fields as $name => $field) { + if (!is_string($field)) { + continue; + } + + $field = $this->items[$field]; + + // Skip ignored field, it will not be required. + if (!empty($field['disabled']) || !empty($field['validate']['ignore'])) { + continue; + } + + // Skip overridable fields without value. + // TODO: We need better overridable support, which is not just ignoring required values but also looking if defaults are good. + if (!empty($field['overridable']) && !isset($data[$name])) { + continue; + } + + // Check if required. + if (isset($field['validate']['required']) + && $field['validate']['required'] === true) { + if (isset($data[$name])) { + continue; + } + if ($field['type'] === 'file' && isset($data['data']['name'][$name])) { //handle case of file input fields required + continue; + } + + $value = $field['label'] ?? $field['name']; + $language = Grav::instance()['language']; + $message = sprintf($language->translate('GRAV.FORM.MISSING_REQUIRED_FIELD', null, true) . ' %s', $language->translate($value)); + $messages[$field['name']][] = $message; + } + } + + return $messages; + } + + /** + * @param array $field + * @param string $property + * @param array $call + * @return void + */ + protected function dynamicConfig(array &$field, $property, array &$call) + { + $value = $call['params']; + + $default = $field[$property] ?? null; + $config = Grav::instance()['config']->get($value, $default); + + if (null !== $config) { + $field[$property] = $config; + } + } +} diff --git a/system/src/Grav/Common/Data/Blueprints.php b/system/src/Grav/Common/Data/Blueprints.php new file mode 100644 index 0000000..cef67d1 --- /dev/null +++ b/system/src/Grav/Common/Data/Blueprints.php @@ -0,0 +1,121 @@ +search = $search; + } + + /** + * Get blueprint. + * + * @param string $type Blueprint type. + * @return Blueprint + * @throws RuntimeException + */ + public function get($type) + { + if (!isset($this->instances[$type])) { + $blueprint = $this->loadFile($type); + $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 = []; + + $grav = Grav::instance(); + + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + + // Get stream / directory iterator. + if ($locator->isStream($this->search)) { + $iterator = $locator->getIterator($this->search); + } else { + $iterator = new DirectoryIterator($this->search); + } + + foreach ($iterator as $file) { + if (!$file->isFile() || '.' . $file->getExtension() !== YAML_EXT) { + continue; + } + $name = $file->getBasename(YAML_EXT); + $this->types[$name] = ucfirst(str_replace('_', ' ', $name)); + } + } + + return $this->types; + } + + + /** + * Load blueprint file. + * + * @param string $name Name of the blueprint. + * @return Blueprint + */ + protected function loadFile($name) + { + $blueprint = new Blueprint($name); + + if (is_array($this->search) || is_object($this->search)) { + // Page types. + $blueprint->setOverrides($this->search); + $blueprint->setContext('blueprints://pages'); + } else { + $blueprint->setContext($this->search); + } + + try { + $blueprint->load()->init(); + } catch (RuntimeException $e) { + $log = Grav::instance()['log']; + $log->error(sprintf('Blueprint %s cannot be loaded: %s', $name, $e->getMessage())); + + throw $e; + } + + return $blueprint; + } +} diff --git a/system/src/Grav/Common/Data/Data.php b/system/src/Grav/Common/Data/Data.php new file mode 100644 index 0000000..5a91516 --- /dev/null +++ b/system/src/Grav/Common/Data/Data.php @@ -0,0 +1,343 @@ +items = $items; + if (null !== $blueprints) { + $this->blueprints = $blueprints; + } + } + + /** + * @param bool $value + * @return $this + */ + public function setKeepEmptyValues(bool $value) + { + $this->keepEmptyValues = $value; + + return $this; + } + + /** + * @param bool $value + * @return $this + */ + public function setMissingValuesAsNull(bool $value) + { + $this->missingValuesAsNull = $value; + + return $this; + } + + /** + * Get value by using dot notation for nested arrays/objects. + * + * @example $value = $data->value('this.is.my.nested.variable'); + * + * @param string $name Dot separated path to the requested value. + * @param mixed $default Default value (or null). + * @param string $separator Separator, defaults to '.' + * @return mixed Value. + */ + public function value($name, $default = null, $separator = '.') + { + return $this->get($name, $default, $separator); + } + + /** + * Join nested values together by using blueprints. + * + * @param string $name Dot separated path to the requested value. + * @param mixed $value Value to be joined. + * @param string $separator Separator, defaults to '.' + * @return $this + * @throws RuntimeException + */ + public function join($name, $value, $separator = '.') + { + $old = $this->get($name, null, $separator); + if ($old !== null) { + if (!is_array($old)) { + throw new RuntimeException('Value ' . $old); + } + + if (is_object($value)) { + $value = (array) $value; + } elseif (!is_array($value)) { + throw new RuntimeException('Value ' . $value); + } + + $value = $this->blueprints()->mergeData($old, $value, $name, $separator); + } + + $this->set($name, $value, $separator); + + return $this; + } + + /** + * Get nested structure containing default values defined in the blueprints. + * + * Fields without default value are ignored in the list. + + * @return array + */ + public function getDefaults() + { + return $this->blueprints()->getDefaults(); + } + + /** + * Set default values by using blueprints. + * + * @param string $name Dot separated path to the requested value. + * @param mixed $value Value to be joined. + * @param string $separator Separator, defaults to '.' + * @return $this + */ + public function joinDefaults($name, $value, $separator = '.') + { + if (is_object($value)) { + $value = (array) $value; + } + + $old = $this->get($name, null, $separator); + if ($old !== null) { + $value = $this->blueprints()->mergeData($value, $old, $name, $separator); + } + + $this->set($name, $value, $separator); + + return $this; + } + + /** + * Get value from the configuration and join it with given data. + * + * @param string $name Dot separated path to the requested value. + * @param array|object $value Value to be joined. + * @param string $separator Separator, defaults to '.' + * @return array + * @throws RuntimeException + */ + public function getJoined($name, $value, $separator = '.') + { + if (is_object($value)) { + $value = (array) $value; + } elseif (!is_array($value)) { + throw new RuntimeException('Value ' . $value); + } + + $old = $this->get($name, null, $separator); + + if ($old === null) { + // No value set; no need to join data. + return $value; + } + + if (!is_array($old)) { + throw new RuntimeException('Value ' . $old); + } + + // Return joined data. + return $this->blueprints()->mergeData($old, $value, $name, $separator); + } + + + /** + * Merge two configurations together. + * + * @param array $data + * @return $this + */ + public function merge(array $data) + { + $this->items = $this->blueprints()->mergeData($this->items, $data); + + return $this; + } + + /** + * Set default values to the configuration if variables were not set. + * + * @param array $data + * @return $this + */ + public function setDefaults(array $data) + { + $this->items = $this->blueprints()->mergeData($data, $this->items); + + return $this; + } + + /** + * Validate by blueprints. + * + * @return $this + * @throws Exception + */ + public function validate() + { + $this->blueprints()->validate($this->items); + + return $this; + } + + /** + * @return $this + */ + public function filter() + { + $args = func_get_args(); + $missingValuesAsNull = (bool)(array_shift($args) ?? $this->missingValuesAsNull); + $keepEmptyValues = (bool)(array_shift($args) ?? $this->keepEmptyValues); + + $this->items = $this->blueprints()->filter($this->items, $missingValuesAsNull, $keepEmptyValues); + + return $this; + } + + /** + * Get extra items which haven't been defined in blueprints. + * + * @return array + */ + public function extra() + { + return $this->blueprints()->extra($this->items); + } + + /** + * Return blueprints. + * + * @return Blueprint + */ + public function blueprints() + { + if (null === $this->blueprints) { + $this->blueprints = new Blueprint(); + } elseif (is_callable($this->blueprints)) { + // Lazy load blueprints. + $blueprints = $this->blueprints; + $this->blueprints = $blueprints(); + } + + return $this->blueprints; + } + + /** + * Save data if storage has been defined. + * + * @return void + * @throws RuntimeException + */ + 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() + { + $file = $this->file(); + + return $file && $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() + { + $file = $this->file(); + + return $file ? $file->raw() : ''; + } + + /** + * Set or get the data storage. + * + * @param FileInterface|null $storage Optionally enter a new storage. + * @return FileInterface|null + */ + public function file(FileInterface $storage = null) + { + if ($storage) { + $this->storage = $storage; + } + + return $this->storage; + } + + /** + * @return array + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->items; + } +} diff --git a/system/src/Grav/Common/Data/DataInterface.php b/system/src/Grav/Common/Data/DataInterface.php new file mode 100644 index 0000000..ed33692 --- /dev/null +++ b/system/src/Grav/Common/Data/DataInterface.php @@ -0,0 +1,84 @@ +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. + * + * @return Blueprint + */ + public function blueprints(); + + /** + * Validate by blueprints. + * + * @return $this + * @throws Exception + */ + public function validate(); + + /** + * Filter all items by using blueprints. + * + * @return $this + */ + public function filter(); + + /** + * Get extra items which haven't been defined in blueprints. + * + * @return array + */ + public function extra(); + + /** + * Save data into the file. + * + * @return void + */ + public function save(); + + /** + * Set or get the data storage. + * + * @param FileInterface|null $storage Optionally enter a new storage. + * @return FileInterface + */ + public function file(FileInterface $storage = null); +} diff --git a/system/src/Grav/Common/Data/Validation.php b/system/src/Grav/Common/Data/Validation.php new file mode 100644 index 0000000..e668f61 --- /dev/null +++ b/system/src/Grav/Common/Data/Validation.php @@ -0,0 +1,1238 @@ +translate($field['validate']['message']) + : $language->translate('GRAV.FORM.INVALID_INPUT') . ' "' . $language->translate($name) . '"'; + + + // Validate type with fallback type text. + $method = 'type' . str_replace('-', '_', $type); + + // If this is a YAML field validate/filter as such + if (isset($field['yaml']) && $field['yaml'] === true) { + $method = 'typeYaml'; + } + + $messages = []; + + $success = method_exists(__CLASS__, $method) ? self::$method($value, $validate, $field) : true; + if (!$success) { + $messages[$field['name']][] = $message; + } + + // Check individual rules. + foreach ($validate as $rule => $params) { + $method = 'validate' . ucfirst(str_replace('-', '_', $rule)); + + if (method_exists(__CLASS__, $method)) { + $success = self::$method($value, $params); + + if (!$success) { + $messages[$field['name']][] = $message; + } + } + } + + return $messages; + } + + /** + * @param mixed $value + * @param array $field + * @return array + */ + public static function checkSafety($value, array $field) + { + $messages = []; + + $type = $field['validate']['type'] ?? $field['type'] ?? 'text'; + $options = $field['xss_check'] ?? []; + if ($options === false || $type === 'unset') { + return $messages; + } + if (!is_array($options)) { + $options = []; + } + + $name = ucfirst($field['label'] ?? $field['name'] ?? 'UNKNOWN'); + + /** @var UserInterface $user */ + $user = Grav::instance()['user'] ?? null; + /** @var Config $config */ + $config = Grav::instance()['config']; + + $xss_whitelist = $config->get('security.xss_whitelist', 'admin.super'); + + // Get language class. + /** @var Language $language */ + $language = Grav::instance()['language']; + + if (!static::authorize($xss_whitelist, $user)) { + $defaults = Security::getXssDefaults(); + $options += $defaults; + $options['enabled_rules'] += $defaults['enabled_rules']; + if (!empty($options['safe_protocols'])) { + $options['invalid_protocols'] = array_diff($options['invalid_protocols'], $options['safe_protocols']); + } + if (!empty($options['safe_tags'])) { + $options['dangerous_tags'] = array_diff($options['dangerous_tags'], $options['safe_tags']); + } + + if (is_string($value)) { + $violation = Security::detectXss($value, $options); + if ($violation) { + $messages[$name][] = $language->translate(['GRAV.FORM.XSS_ISSUES', $language->translate($name)], null, true); + } + } elseif (is_array($value)) { + $violations = Security::detectXssFromArray($value, "{$name}.", $options); + if ($violations) { + $messages[$name][] = $language->translate(['GRAV.FORM.XSS_ISSUES', $language->translate($name)], null, true); + } + } + } + + return $messages; + } + + /** + * Checks user authorisation to the action. + * + * @param string|string[] $action + * @param UserInterface|null $user + * @return bool + */ + public static function authorize($action, UserInterface $user = null) + { + if (!$user) { + return false; + } + + $action = (array)$action; + foreach ($action as $a) { + // Ignore 'admin.super' if it's not the only value to be checked. + if ($a === 'admin.super' && count($action) > 1 && $user instanceof FlexObjectInterface) { + continue; + } + + if ($user->authorize($a)) { + return true; + } + } + + return false; + } + + /** + * 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 = (array)($field['filter'] ?? $field['validate'] ?? null); + + // If value isn't required, we will return null if empty value is given. + if (($value === null || $value === '') && empty($validate['required'])) { + return null; + } + + if (!isset($field['type'])) { + $field['type'] = 'text'; + } + $type = $field['filter']['type'] ?? $field['validate']['type'] ?? $field['type']; + + $method = 'filter' . ucfirst(str_replace('-', '_', $type)); + + // If this is a YAML field validate/filter as such + if (isset($field['yaml']) && $field['yaml'] === true) { + $method = 'filterYaml'; + } + + if (!method_exists(__CLASS__, $method)) { + $method = isset($field['array']) && $field['array'] === true ? 'filterArray' : 'filterText'; + } + + return self::$method($value, $validate, $field); + } + + /** + * 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) && !is_numeric($value)) { + return false; + } + + $value = (string)$value; + + if (!empty($params['trim'])) { + $value = trim($value); + } + + $value = preg_replace("/\r\n|\r/um", "\n", $value); + $len = mb_strlen($value); + + $min = (int)($params['min'] ?? 0); + if ($min && $len < $min) { + return false; + } + + $multiline = isset($params['multiline']) && $params['multiline']; + + $max = (int)($params['max'] ?? ($multiline ? 65536 : 2048)); + if ($max && $len > $max) { + return false; + } + + $step = (int)($params['step'] ?? 0); + if ($step && ($len - $min) % $step === 0) { + return false; + } + + if (!$multiline && preg_match('/\R/um', $value)) { + return false; + } + + return true; + } + + /** + * @param mixed $value + * @param array $params + * @param array $field + * @return string + */ + protected static function filterText($value, array $params, array $field) + { + if (!is_string($value) && !is_numeric($value)) { + return ''; + } + + $value = (string)$value; + + if (!empty($params['trim'])) { + $value = trim($value); + } + + return preg_replace("/\r\n|\r/um", "\n", $value); + } + + /** + * @param mixed $value + * @param array $params + * @param array $field + * @return string|null + */ + protected static function filterCheckbox($value, array $params, array $field) + { + $value = (string)$value; + $field_value = (string)($field['value'] ?? '1'); + + return $value === $field_value ? $value : null; + } + + /** + * @param mixed $value + * @param array $params + * @param array $field + * @return array|array[]|false|string[] + */ + protected static function filterCommaList($value, array $params, array $field) + { + return is_array($value) ? $value : preg_split('/\s*,\s*/', $value, -1, PREG_SPLIT_NO_EMPTY); + } + + /** + * @param mixed $value + * @param array $params + * @param array $field + * @return bool + */ + public static function typeCommaList($value, array $params, array $field) + { + if (!isset($params['max'])) { + $params['max'] = 2048; + } + + return is_array($value) ? true : self::typeText($value, $params, $field); + } + + /** + * @param mixed $value + * @param array $params + * @param array $field + * @return array|array[]|false|string[] + */ + protected static function filterLines($value, array $params, array $field) + { + return is_array($value) ? $value : preg_split('/\s*[\r\n]+\s*/', $value, -1, PREG_SPLIT_NO_EMPTY); + } + + /** + * @param mixed $value + * @param array $params + * @return string + */ + protected static function filterLower($value, array $params) + { + return mb_strtolower($value); + } + + /** + * @param mixed $value + * @param array $params + * @return string + */ + protected static function filterUpper($value, array $params) + { + return mb_strtoupper($value); + } + + + /** + * 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) + { + if (!isset($params['max'])) { + $params['max'] = 256; + } + + 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) + { + // Set multiple: true so checkboxes can easily use min/max counts to control number of options required + $field['multiple'] = true; + + return self::typeArray((array) $value, $params, $field); + } + + /** + * @param mixed $value + * @param array $params + * @param array $field + * @return array|null + */ + 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; + $field_value = (string)($field['value'] ?? '1'); + + return $value === $field_value; + } + + /** + * 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) + { + if (is_bool($value)) { + $value = (int)$value; + } + + return self::typeArray((array) $value, $params, $field); + } + + /** + * Custom input: file + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeFile($value, array $params, array $field) + { + return self::typeArray((array)$value, $params, $field); + } + + /** + * @param mixed $value + * @param array $params + * @param array $field + * @return array + */ + protected static function filterFile($value, array $params, array $field) + { + return (array)$value; + } + + /** + * HTML5 input: select + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeSelect($value, array $params, array $field) + { + return self::typeArray((array) $value, $params, $field); + } + + /** + * HTML5 input: number + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeNumber($value, array $params, array $field) + { + if (!is_numeric($value)) { + return false; + } + + $value = (float)$value; + + $min = 0; + if (isset($params['min'])) { + $min = (float)$params['min']; + if ($value < $min) { + return false; + } + } + + if (isset($params['max'])) { + $max = (float)$params['max']; + if ($value > $max) { + return false; + } + } + + if (isset($params['step'])) { + $step = (float)$params['step']; + // Count of how many steps we are above/below the minimum value. + $pos = ($value - $min) / $step; + $pos = round($pos, 10); + return is_int(static::filterNumber($pos, $params, $field)); + } + + return true; + } + + /** + * @param mixed $value + * @param array $params + * @param array $field + * @return float|int + */ + protected static function filterNumber($value, array $params, array $field) + { + return (string)(int)$value !== (string)(float)$value ? (float)$value : (int)$value; + } + + /** + * @param mixed $value + * @param array $params + * @param array $field + * @return string + */ + protected static function filterDateTime($value, array $params, array $field) + { + $format = Grav::instance()['config']->get('system.pages.dateformat.default'); + if ($format) { + $converted = new DateTime($value); + return $converted->format($format); + } + return $value; + } + + /** + * HTML5 input: range + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeRange($value, array $params, array $field) + { + return self::typeNumber($value, $params, $field); + } + + /** + * @param mixed $value + * @param array $params + * @param array $field + * @return float|int + */ + 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 (bool)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) + { + if (empty($value)) { + return false; + } + + if (!isset($params['max'])) { + $params['max'] = 320; + } + + $values = !is_array($value) ? explode(',', preg_replace('/\s+/', '', $value)) : $value; + + foreach ($values as $val) { + if (!(self::typeText($val, $params, $field) && strpos($val, '@', 1))) { + return false; + } + } + + return true; + } + + /** + * 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) + { + if (!isset($params['max'])) { + $params['max'] = 2048; + } + + return self::typeText($value, $params, $field) && filter_var($value, FILTER_VALIDATE_URL); + } + + /** + * HTML5 input: datetime + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeDatetime($value, array $params, array $field) + { + if ($value instanceof DateTime) { + return true; + } + if (!is_string($value)) { + return false; + } + if (!isset($params['format'])) { + return false !== strtotime($value); + } + + $dateFromFormat = DateTime::createFromFormat($params['format'], $value); + + return $dateFromFormat && $value === date($params['format'], $dateFromFormat->getTimestamp()); + } + + /** + * HTML5 input: datetime-local + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeDatetimeLocal($value, array $params, array $field) + { + return self::typeDatetime($value, $params, $field); + } + + /** + * HTML5 input: date + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeDate($value, array $params, array $field) + { + 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) + { + 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) + { + 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 = $params['min'] ?? 0; + if (isset($params['step']) && (count($value) - $min) % $params['step'] === 0) { + return false; + } + } + + // If creating new values is allowed, no further checks are needed. + $validateOptions = $field['validate']['options'] ?? null; + if (!empty($field['selectize']['create']) || $validateOptions === 'ignore') { + return true; + } + + $options = $field['options'] ?? []; + $use = $field['use'] ?? 'values'; + + if ($validateOptions) { + // Use custom options structure. + foreach ($options as &$option) { + $option = $option[$validateOptions] ?? null; + } + unset($option); + $options = array_values($options); + } elseif (empty($field['selectize']) || empty($field['multiple'])) { + $options = array_keys($options); + } + if ($use === 'keys') { + $value = array_keys($value); + } + + return !($options && array_diff($value, $options)); + } + + /** + * @param mixed $value + * @param array $params + * @param array $field + * @return array|null + */ + protected static function filterFlatten_array($value, $params, $field) + { + $value = static::filterArray($value, $params, $field); + + return is_array($value) ? Utils::arrayUnflattenDotNotation($value) : null; + } + + /** + * @param mixed $value + * @param array $params + * @param array $field + * @return array|null + */ + protected static function filterArray($value, $params, $field) + { + $values = (array) $value; + $options = isset($field['options']) ? array_keys($field['options']) : []; + $multi = $field['multiple'] ?? false; + + if (count($values) === 1 && isset($values[0]) && $values[0] === '') { + return null; + } + + + if ($options) { + $useKey = isset($field['use']) && $field['use'] === 'keys'; + foreach ($values as $key => $val) { + $values[$key] = $useKey ? (bool) $val : $val; + } + } + + if ($multi) { + foreach ($values as $key => $val) { + if (is_array($val)) { + $val = implode(',', $val); + $values[$key] = array_map('trim', explode(',', $val)); + } else { + $values[$key] = trim($val); + } + } + } + + $ignoreEmpty = isset($field['ignore_empty']) && Utils::isPositive($field['ignore_empty']); + $valueType = $params['value_type'] ?? null; + $keyType = $params['key_type'] ?? null; + if ($ignoreEmpty || $valueType || $keyType) { + $values = static::arrayFilterRecurse($values, ['value_type' => $valueType, 'key_type' => $keyType, 'ignore_empty' => $ignoreEmpty]); + } + + return $values; + } + + /** + * @param array $values + * @param array $params + * @return array + */ + protected static function arrayFilterRecurse(array $values, array $params): array + { + foreach ($values as $key => &$val) { + if ($params['key_type']) { + switch ($params['key_type']) { + case 'int': + $result = is_int($key); + break; + case 'string': + $result = is_string($key); + break; + default: + $result = false; + } + if (!$result) { + unset($values[$key]); + } + } + if (is_array($val)) { + $val = static::arrayFilterRecurse($val, $params); + if ($params['ignore_empty'] && empty($val)) { + unset($values[$key]); + } + } else { + if ($params['value_type'] && $val !== '' && $val !== null) { + switch ($params['value_type']) { + case 'bool': + if (Utils::isPositive($val)) { + $val = true; + } elseif (Utils::isNegative($val)) { + $val = false; + } else { + // Ignore invalid bool values. + $val = null; + } + break; + case 'int': + $val = (int)$val; + break; + case 'float': + $val = (float)$val; + break; + case 'string': + $val = (string)$val; + break; + case 'trim': + $val = trim($val); + break; + } + } + + if ($params['ignore_empty'] && ($val === '' || $val === null)) { + unset($values[$key]); + } + } + } + + return $values; + } + + /** + * @param mixed $value + * @param array $params + * @param array $field + * @return bool + */ + public static function typeList($value, array $params, array $field) + { + if (!is_array($value)) { + return false; + } + + if (isset($field['fields'])) { + foreach ($value as $key => $item) { + foreach ($field['fields'] as $subKey => $subField) { + $subKey = trim($subKey, '.'); + $subValue = $item[$subKey] ?? null; + self::validate($subValue, $subField); + } + } + } + + return true; + } + + /** + * @param mixed $value + * @param array $params + * @param array $field + * @return array + */ + protected static function filterList($value, array $params, array $field) + { + return (array) $value; + } + + /** + * @param mixed $value + * @param array $params + * @return array + */ + public static function filterYaml($value, $params) + { + if (!is_string($value)) { + return $value; + } + + return (array) Yaml::parse($value); + } + + /** + * Custom input: ignore (will not validate) + * + * @param mixed $value Value to be validated. + * @param array $params Validation parameters. + * @param array $field Blueprint for the field. + * @return bool True if validation succeeded. + */ + public static function typeIgnore($value, array $params, array $field) + { + return true; + } + + /** + * @param mixed $value + * @param array $params + * @param array $field + * @return mixed + */ + public static function filterIgnore($value, array $params, array $field) + { + return $value; + } + + /** + * Input value which can be ignored. + * + * @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 typeUnset($value, array $params, array $field) + { + return true; + } + + /** + * @param mixed $value + * @param array $params + * @param array $field + * @return null + */ + public static function filterUnset($value, array $params, array $field) + { + return null; + } + + // HTML5 attributes (min, max and range are handled inside the types) + + /** + * @param mixed $value + * @param bool $params + * @return bool + */ + public static function validateRequired($value, $params) + { + if (is_scalar($value)) { + return (bool) $params !== true || $value !== ''; + } + + return (bool) $params !== true || !empty($value); + } + + /** + * @param mixed $value + * @param string $params + * @return bool + */ + public static function validatePattern($value, $params) + { + return (bool) preg_match("`^{$params}$`u", $value); + } + + // Internal types + + /** + * @param mixed $value + * @param mixed $params + * @return bool + */ + public static function validateAlpha($value, $params) + { + return ctype_alpha($value); + } + + /** + * @param mixed $value + * @param mixed $params + * @return bool + */ + public static function validateAlnum($value, $params) + { + return ctype_alnum($value); + } + + /** + * @param mixed $value + * @param mixed $params + * @return bool + */ + public static function typeBool($value, $params) + { + return is_bool($value) || $value == 1 || $value == 0; + } + + /** + * @param mixed $value + * @param mixed $params + * @return bool + */ + public static function validateBool($value, $params) + { + return is_bool($value) || $value == 1 || $value == 0; + } + + /** + * @param mixed $value + * @param mixed $params + * @return bool + */ + protected static function filterBool($value, $params) + { + return (bool) $value; + } + + /** + * @param mixed $value + * @param mixed $params + * @return bool + */ + public static function validateDigit($value, $params) + { + return ctype_digit($value); + } + + /** + * @param mixed $value + * @param mixed $params + * @return bool + */ + public static function validateFloat($value, $params) + { + return is_float(filter_var($value, FILTER_VALIDATE_FLOAT)); + } + + /** + * @param mixed $value + * @param mixed $params + * @return float + */ + protected static function filterFloat($value, $params) + { + return (float) $value; + } + + /** + * @param mixed $value + * @param mixed $params + * @return bool + */ + public static function validateHex($value, $params) + { + return ctype_xdigit($value); + } + + /** + * Custom input: int + * + * @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 typeInt($value, array $params, array $field) + { + $params['step'] = max(1, (int)($params['step'] ?? 0)); + + return self::typeNumber($value, $params, $field); + } + + /** + * @param mixed $value + * @param mixed $params + * @return bool + */ + public static function validateInt($value, $params) + { + return is_numeric($value) && (int)$value == $value; + } + + /** + * @param mixed $value + * @param mixed $params + * @return int + */ + protected static function filterInt($value, $params) + { + return (int)$value; + } + + /** + * @param mixed $value + * @param mixed $params + * @return bool + */ + public static function validateArray($value, $params) + { + return is_array($value) || ($value instanceof ArrayAccess && $value instanceof Traversable && $value instanceof Countable); + } + + /** + * @param mixed $value + * @param mixed $params + * @return array + */ + public static function filterItem_List($value, $params) + { + return array_values(array_filter($value, static function ($v) { + return !empty($v); + })); + } + + /** + * @param mixed $value + * @param mixed $params + * @return bool + */ + public static function validateJson($value, $params) + { + return (bool) (@json_decode($value)); + } +} diff --git a/system/src/Grav/Common/Data/ValidationException.php b/system/src/Grav/Common/Data/ValidationException.php new file mode 100644 index 0000000..14b4bef --- /dev/null +++ b/system/src/Grav/Common/Data/ValidationException.php @@ -0,0 +1,67 @@ +messages = $messages; + + $language = Grav::instance()['language']; + $this->message = $language->translate('GRAV.FORM.VALIDATION_FAIL', null, true) . ' ' . $this->message; + + foreach ($messages as $list) { + $list = array_unique($list); + foreach ($list as $message) { + $this->message .= '
    ' . htmlspecialchars($message, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + } + } + + return $this; + } + + public function setSimpleMessage(bool $escape = true): void + { + $first = reset($this->messages); + $message = reset($first); + + $this->message = $escape ? htmlspecialchars($message, ENT_QUOTES | ENT_HTML5, 'UTF-8') : $message; + } + + /** + * @return array + */ + public function getMessages(): array + { + return $this->messages; + } + + public function jsonSerialize(): array + { + return ['validation' => $this->messages]; + } +} diff --git a/system/src/Grav/Common/Debugger.php b/system/src/Grav/Common/Debugger.php new file mode 100644 index 0000000..a68edb8 --- /dev/null +++ b/system/src/Grav/Common/Debugger.php @@ -0,0 +1,1148 @@ +currentTime = microtime(true); + + if (!defined('GRAV_REQUEST_TIME')) { + define('GRAV_REQUEST_TIME', $this->currentTime); + } + + $this->requestTime = $_SERVER['REQUEST_TIME_FLOAT'] ?? GRAV_REQUEST_TIME; + + // Set deprecation collector. + $this->setErrorHandler(); + } + + /** + * @return Clockwork|null + */ + public function getClockwork(): ?Clockwork + { + return $this->enabled ? $this->clockwork : null; + } + + /** + * Initialize the debugger + * + * @return $this + * @throws DebugBarException + */ + public function init() + { + if ($this->initialized) { + return $this; + } + + $this->grav = Grav::instance(); + $this->config = $this->grav['config']; + + // Enable/disable debugger based on configuration. + $this->enabled = (bool)$this->config->get('system.debugger.enabled'); + $this->censored = (bool)$this->config->get('system.debugger.censored', false); + + if ($this->enabled) { + $this->initialized = true; + + $clockwork = $debugbar = null; + + switch ($this->config->get('system.debugger.provider', 'debugbar')) { + case 'clockwork': + $this->clockwork = $clockwork = new Clockwork(); + break; + default: + $this->debugbar = $debugbar = new DebugBar(); + } + + $plugins_config = (array)$this->config->get('plugins'); + ksort($plugins_config); + + if ($clockwork) { + $log = $this->grav['log']; + $clockwork->setStorage(new FileStorage('cache://clockwork')); + if (extension_loaded('xdebug')) { + $clockwork->addDataSource(new XdebugDataSource()); + } + if ($log instanceof Logger) { + $clockwork->addDataSource(new MonologDataSource($log)); + } + + $timeline = $clockwork->timeline(); + if ($this->requestTime !== GRAV_REQUEST_TIME) { + $event = $timeline->event('Server'); + $event->finalize($this->requestTime, GRAV_REQUEST_TIME); + } + if ($this->currentTime !== GRAV_REQUEST_TIME) { + $event = $timeline->event('Loading'); + $event->finalize(GRAV_REQUEST_TIME, $this->currentTime); + } + $event = $timeline->event('Site Setup'); + $event->finalize($this->currentTime, microtime(true)); + } + + if ($this->censored) { + $censored = ['CENSORED' => true]; + } + + if ($debugbar) { + $debugbar->addCollector(new PhpInfoCollector()); + $debugbar->addCollector(new MessagesCollector()); + if (!$this->censored) { + $debugbar->addCollector(new RequestDataCollector()); + } + $debugbar->addCollector(new TimeDataCollector($this->requestTime)); + $debugbar->addCollector(new MemoryCollector()); + $debugbar->addCollector(new ExceptionsCollector()); + $debugbar->addCollector(new ConfigCollector($censored ?? (array)$this->config->get('system'), 'Config')); + $debugbar->addCollector(new ConfigCollector($censored ?? $plugins_config, 'Plugins')); + $debugbar->addCollector(new ConfigCollector($this->config->get('streams.schemes'), 'Streams')); + + if ($this->requestTime !== GRAV_REQUEST_TIME) { + $debugbar['time']->addMeasure('Server', $debugbar['time']->getRequestStartTime(), GRAV_REQUEST_TIME); + } + if ($this->currentTime !== GRAV_REQUEST_TIME) { + $debugbar['time']->addMeasure('Loading', GRAV_REQUEST_TIME, $this->currentTime); + } + $debugbar['time']->addMeasure('Site Setup', $this->currentTime, microtime(true)); + } + + $this->addMessage('Grav v' . GRAV_VERSION . ' - PHP ' . PHP_VERSION); + $this->config->debug(); + + if ($clockwork) { + $clockwork->info('System Configuration', $censored ?? $this->config->get('system')); + $clockwork->info('Plugins Configuration', $censored ?? $plugins_config); + $clockwork->info('Streams', $this->config->get('streams.schemes')); + } + } + + return $this; + } + + public function finalize(): void + { + if ($this->clockwork && $this->enabled) { + $this->stopProfiling('Profiler Analysis'); + $this->addMeasures(); + + $deprecations = $this->getDeprecations(); + $count = count($deprecations); + if (!$count) { + return; + } + + /** @var UserData $userData */ + $userData = $this->clockwork->userData('Deprecated'); + $userData->counters([ + 'Deprecated' => count($deprecations) + ]); + /* + foreach ($deprecations as &$deprecation) { + $d = $deprecation; + unset($d['message']); + $this->clockwork->log('deprecated', $deprecation['message'], $d); + } + unset($deprecation); + */ + + $userData->table('Your site is using following deprecated features', $deprecations); + } + } + + public function logRequest(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface + { + if (!$this->enabled || !$this->clockwork) { + return $response; + } + + $clockwork = $this->clockwork; + + $this->finalize(); + + $clockwork->timeline()->finalize($request->getAttribute('request_time')); + + if ($this->censored) { + $censored = 'CENSORED'; + $request = $request + ->withCookieParams([$censored => '']) + ->withUploadedFiles([]) + ->withHeader('cookie', $censored); + $request = $request->withParsedBody([$censored => '']); + } + + $clockwork->addDataSource(new PsrMessageDataSource($request, $response)); + + $clockwork->resolveRequest(); + $clockwork->storeRequest(); + + $clockworkRequest = $clockwork->getRequest(); + + $response = $response + ->withHeader('X-Clockwork-Id', $clockworkRequest->id) + ->withHeader('X-Clockwork-Version', $clockwork::VERSION); + + $response = $response->withHeader('X-Clockwork-Path', Utils::url('/__clockwork/')); + + return $response->withHeader('Server-Timing', ServerTiming::fromRequest($clockworkRequest)->value()); + } + + + public function debuggerRequest(RequestInterface $request): Response + { + $clockwork = $this->clockwork; + + $headers = [ + 'Content-Type' => 'application/json', + 'Grav-Internal-SkipShutdown' => 1 + ]; + + $path = $request->getUri()->getPath(); + $clockworkDataUri = '#/__clockwork(?:/(?[0-9-]+))?(?:/(?(?:previous|next)))?(?:/(?\d+))?#'; + if (preg_match($clockworkDataUri, $path, $matches) === false) { + $response = ['message' => 'Bad Input']; + + return new Response(400, $headers, json_encode($response)); + } + + $id = $matches['id'] ?? null; + $direction = $matches['direction'] ?? 'latest'; + $count = $matches['count'] ?? null; + + $storage = $clockwork->getStorage(); + + if ($direction === 'previous') { + $data = $storage->previous($id, $count); + } elseif ($direction === 'next') { + $data = $storage->next($id, $count); + } elseif ($direction === 'latest' || $id === 'latest') { + $data = $storage->latest(); + } else { + $data = $storage->find($id); + } + + if (preg_match('#(?[0-9-]+|latest)/extended#', $path)) { + $clockwork->extendRequest($data); + } + + if (!$data) { + $response = ['message' => 'Not Found']; + + return new Response(404, $headers, json_encode($response)); + } + + $data = is_array($data) ? array_map(static function ($item) { + return $item->toArray(); + }, $data) : $data->toArray(); + + return new Response(200, $headers, json_encode($data)); + } + + /** + * @return void + */ + protected function addMeasures(): void + { + if (!$this->enabled) { + return; + } + + $nowTime = microtime(true); + $clkTimeLine = $this->clockwork ? $this->clockwork->timeline() : null; + $debTimeLine = $this->debugbar ? $this->debugbar['time'] : null; + foreach ($this->timers as $name => $data) { + $description = $data[0]; + $startTime = $data[1] ?? null; + $endTime = $data[2] ?? $nowTime; + if ($clkTimeLine) { + $event = $clkTimeLine->event($description); + $event->finalize($startTime, $endTime); + } elseif ($debTimeLine) { + if ($endTime - $startTime < 0.001) { + continue; + } + + $debTimeLine->addMeasure($description ?? $name, $startTime, $endTime); + } + } + $this->timers = []; + } + + /** + * Set/get the enabled state of the debugger + * + * @param bool|null $state If null, the method returns the enabled value. If set, the method sets the enabled state + * @return bool + */ + public function enabled($state = null) + { + if ($state !== null) { + $this->enabled = (bool)$state; + } + + return $this->enabled; + } + + /** + * Add the debugger assets to the Grav Assets + * + * @return $this + */ + public function addAssets() + { + if ($this->enabled) { + // Only add assets if Page is HTML + $page = $this->grav['page']; + if ($page->templateFormat() !== 'html') { + return $this; + } + + /** @var Assets $assets */ + $assets = $this->grav['assets']; + + // Clockwork specific assets + if ($this->clockwork) { + if ($this->config->get('plugins.clockwork-web.enabled')) { + $route = Utils::url($this->grav['config']->get('plugins.clockwork-web.route')); + } else { + $route = 'https://github.com/getgrav/grav-plugin-clockwork-web'; + } + $assets->addCss('/system/assets/debugger/clockwork.css'); + $assets->addJs('/system/assets/debugger/clockwork.js', [ + 'id' => 'clockwork-script', + 'data-route' => $route + ]); + } + + + // Debugbar specific assets + if ($this->debugbar) { + // Add jquery library + $assets->add('jquery', 101); + + $this->renderer = $this->debugbar->getJavascriptRenderer(); + $this->renderer->setIncludeVendors(false); + + [$css_files, $js_files] = $this->renderer->getAssets(null, JavascriptRenderer::RELATIVE_URL); + + foreach ((array)$css_files as $css) { + $assets->addCss($css); + } + + $assets->addCss('/system/assets/debugger/phpdebugbar.css', ['loading' => 'inline']); + + foreach ((array)$js_files as $js) { + $assets->addJs($js); + } + } + } + + return $this; + } + + /** + * @param int $limit + * @return array + */ + public function getCaller($limit = 2) + { + $trace = debug_backtrace(false, $limit); + + return array_pop($trace); + } + + /** + * Adds a data collector + * + * @param DataCollectorInterface $collector + * @return $this + * @throws DebugBarException + */ + public function addCollector($collector) + { + if ($this->debugbar && !$this->debugbar->hasCollector($collector->getName())) { + $this->debugbar->addCollector($collector); + } + + return $this; + } + + /** + * Returns a data collector + * + * @param string $name + * @return DataCollectorInterface|null + * @throws DebugBarException + */ + public function getCollector($name) + { + if ($this->debugbar && $this->debugbar->hasCollector($name)) { + return $this->debugbar->getCollector($name); + } + + return null; + } + + /** + * Displays the debug bar + * + * @return $this + */ + public function render() + { + if ($this->enabled && $this->debugbar) { + // Only add assets if Page is HTML + $page = $this->grav['page']; + if (!$this->renderer || $page->templateFormat() !== 'html') { + return $this; + } + + $this->addMeasures(); + $this->addDeprecations(); + + echo $this->renderer->render(); + } + + return $this; + } + + /** + * Sends the data through the HTTP headers + * + * @return $this + */ + public function sendDataInHeaders() + { + if ($this->enabled && $this->debugbar) { + $this->addMeasures(); + $this->addDeprecations(); + $this->debugbar->sendDataInHeaders(); + } + + return $this; + } + + /** + * Returns collected debugger data. + * + * @return array|null + */ + public function getData() + { + if (!$this->enabled || !$this->debugbar) { + return null; + } + + $this->addMeasures(); + $this->addDeprecations(); + $this->timers = []; + + return $this->debugbar->getData(); + } + + /** + * Hierarchical Profiler support. + * + * @param callable $callable + * @param string|null $message + * @return mixed + */ + public function profile(callable $callable, string $message = null) + { + $this->startProfiling(); + $response = $callable(); + $this->stopProfiling($message); + + return $response; + } + + public function addTwigProfiler(Environment $twig): void + { + $clockwork = $this->getClockwork(); + if ($clockwork) { + $source = new TwigClockworkDataSource($twig); + $source->listenToEvents(); + $clockwork->addDataSource($source); + } + } + + /** + * Start profiling code. + * + * @return void + */ + public function startProfiling(): void + { + if ($this->enabled && extension_loaded('tideways_xhprof')) { + $this->profiling++; + if ($this->profiling === 1) { + // @phpstan-ignore-next-line + \tideways_xhprof_enable(TIDEWAYS_XHPROF_FLAGS_NO_BUILTINS); + } + } + } + + /** + * Stop profiling code. Returns profiling array or null if profiling couldn't be done. + * + * @param string|null $message + * @return array|null + */ + public function stopProfiling(string $message = null): ?array + { + $timings = null; + if ($this->enabled && extension_loaded('tideways_xhprof')) { + $profiling = $this->profiling - 1; + if ($profiling === 0) { + // @phpstan-ignore-next-line + $timings = \tideways_xhprof_disable(); + $timings = $this->buildProfilerTimings($timings); + + if ($this->clockwork) { + /** @var UserData $userData */ + $userData = $this->clockwork->userData('Profiler'); + $userData->counters([ + 'Calls' => count($timings) + ]); + $userData->table('Profiler', $timings); + } else { + $this->addMessage($message ?? 'Profiler Analysis', 'debug', $timings); + } + } + $this->profiling = max(0, $profiling); + } + + return $timings; + } + + /** + * @param array $timings + * @return array + */ + protected function buildProfilerTimings(array $timings): array + { + // Filter method calls which take almost no time. + $timings = array_filter($timings, function ($value) { + return $value['wt'] > 50; + }); + + uasort($timings, function (array $a, array $b) { + return $b['wt'] <=> $a['wt']; + }); + + $table = []; + foreach ($timings as $key => $timing) { + $parts = explode('==>', $key); + $method = $this->parseProfilerCall(array_pop($parts)); + $context = $this->parseProfilerCall(array_pop($parts)); + + // Skip redundant method calls. + if ($context === 'Grav\Framework\RequestHandler\RequestHandler::handle()') { + continue; + } + + // Do not profile library calls. + if (strpos($context, 'Grav\\') !== 0) { + continue; + } + + $table[] = [ + 'Context' => $context, + 'Method' => $method, + 'Calls' => $timing['ct'], + 'Time (ms)' => $timing['wt'] / 1000, + ]; + } + + return $table; + } + + /** + * @param string|null $call + * @return mixed|string|null + */ + protected function parseProfilerCall(?string $call) + { + if (null === $call) { + return ''; + } + if (strpos($call, '@')) { + [$call,] = explode('@', $call); + } + if (strpos($call, '::')) { + [$class, $call] = explode('::', $call); + } + + if (!isset($class)) { + return $call; + } + + // It is also possible to display twig files, but they are being logged in views. + /* + if (strpos($class, '__TwigTemplate_') === 0 && class_exists($class)) { + $env = new Environment(); + / ** @var Template $template * / + $template = new $class($env); + + return $template->getTemplateName(); + } + */ + + return "{$class}::{$call}()"; + } + + /** + * Start a timer with an associated name and description + * + * @param string $name + * @param string|null $description + * @return $this + */ + public function startTimer($name, $description = null) + { + $this->timers[$name] = [$description, microtime(true)]; + + return $this; + } + + /** + * Stop the named timer + * + * @param string $name + * @return $this + */ + public function stopTimer($name) + { + if (isset($this->timers[$name])) { + $endTime = microtime(true); + $this->timers[$name][] = $endTime; + } + + return $this; + } + + /** + * Dump variables into the Messages tab of the Debug Bar + * + * @param mixed $message + * @param string $label + * @param mixed|bool $isString + * @return $this + */ + public function addMessage($message, $label = 'info', $isString = true) + { + if ($this->enabled) { + if ($this->censored) { + if (!is_scalar($message)) { + $message = 'CENSORED'; + } + if (!is_scalar($isString)) { + $isString = ['CENSORED']; + } + } + + if ($this->debugbar) { + if (is_array($isString)) { + $message = $isString; + $isString = false; + } elseif (is_string($isString)) { + $message = $isString; + $isString = true; + } + $this->debugbar['messages']->addMessage($message, $label, $isString); + } + + if ($this->clockwork) { + $context = $isString; + if (!is_scalar($message)) { + $context = $message; + $message = gettype($context); + } + if (is_bool($context)) { + $context = []; + } elseif (!is_array($context)) { + $type = gettype($context); + $context = [$type => $context]; + } + + $this->clockwork->log($label, $message, $context); + } + } + + return $this; + } + + /** + * @param string $name + * @param object $event + * @param EventDispatcherInterface $dispatcher + * @param float|null $time + * @return $this + */ + public function addEvent(string $name, $event, EventDispatcherInterface $dispatcher, float $time = null) + { + if ($this->enabled && $this->clockwork) { + $time = $time ?? microtime(true); + $duration = (microtime(true) - $time) * 1000; + + $data = null; + if ($event && method_exists($event, '__debugInfo')) { + $data = $event; + } + + $listeners = []; + foreach ($dispatcher->getListeners($name) as $listener) { + $listeners[] = $this->resolveCallable($listener); + } + + $this->clockwork->addEvent($name, $data, $time, ['listeners' => $listeners, 'duration' => $duration]); + } + + return $this; + } + + /** + * Dump exception into the Messages tab of the Debug Bar + * + * @param Throwable $e + * @return Debugger + */ + public function addException(Throwable $e) + { + if ($this->initialized && $this->enabled) { + if ($this->debugbar) { + $this->debugbar['exceptions']->addThrowable($e); + } + + if ($this->clockwork) { + /** @var UserData $exceptions */ + $exceptions = $this->clockwork->userData('Exceptions'); + $exceptions->data(['message' => $e->getMessage()]); + + $this->clockwork->alert($e->getMessage(), ['exception' => $e]); + } + } + + return $this; + } + + /** + * @return void + */ + public function setErrorHandler() + { + $this->errorHandler = set_error_handler( + [$this, 'deprecatedErrorHandler'] + ); + } + + /** + * @param int $errno + * @param string $errstr + * @param string $errfile + * @param int $errline + * @return bool + */ + public function deprecatedErrorHandler($errno, $errstr, $errfile, $errline) + { + if ($errno !== E_USER_DEPRECATED && $errno !== E_DEPRECATED) { + if ($this->errorHandler) { + return call_user_func($this->errorHandler, $errno, $errstr, $errfile, $errline); + } + + return true; + } + + if (!$this->enabled) { + return true; + } + + // Figure out error scope from the error. + $scope = 'unknown'; + if (stripos($errstr, 'grav') !== false) { + $scope = 'grav'; + } elseif (strpos($errfile, '/twig/') !== false) { + $scope = 'twig'; + // TODO: remove when upgrading to Twig 2+ + if (str_contains($errstr, '#[\ReturnTypeWillChange]') || str_contains($errstr, 'Passing null to parameter')) { + return true; + } + } elseif (stripos($errfile, '/yaml/') !== false) { + $scope = 'yaml'; + } elseif (strpos($errfile, '/vendor/') !== false) { + $scope = 'vendor'; + } + + // Clean up backtrace to make it more useful. + $backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT); + + // Skip current call. + array_shift($backtrace); + + // Find yaml file where the error happened. + if ($scope === 'yaml') { + foreach ($backtrace as $current) { + if (isset($current['args'])) { + foreach ($current['args'] as $arg) { + if ($arg instanceof SplFileInfo) { + $arg = $arg->getPathname(); + } + if (is_string($arg) && preg_match('/.+\.(yaml|md)$/i', $arg)) { + $errfile = $arg; + $errline = 0; + + break 2; + } + } + } + } + } + + // Filter arguments. + $cut = 0; + $previous = null; + foreach ($backtrace as $i => &$current) { + if (isset($current['args'])) { + $args = []; + foreach ($current['args'] as $arg) { + if (is_string($arg)) { + $arg = "'" . $arg . "'"; + if (mb_strlen($arg) > 100) { + $arg = 'string'; + } + } elseif (is_bool($arg)) { + $arg = $arg ? 'true' : 'false'; + } elseif (is_scalar($arg)) { + $arg = $arg; + } elseif (is_object($arg)) { + $arg = get_class($arg) . ' $object'; + } elseif (is_array($arg)) { + $arg = '$array'; + } else { + $arg = '$object'; + } + + $args[] = $arg; + } + $current['args'] = $args; + } + + $object = $current['object'] ?? null; + unset($current['object']); + + $reflection = null; + if ($object instanceof TemplateWrapper) { + $reflection = new ReflectionObject($object); + $property = $reflection->getProperty('template'); + $property->setAccessible(true); + $object = $property->getValue($object); + } + + if ($object instanceof Template) { + $file = $current['file'] ?? null; + + if (preg_match('`(Template.php|TemplateWrapper.php)$`', $file)) { + $current = null; + continue; + } + + $debugInfo = $object->getDebugInfo(); + + $line = 1; + if (!$reflection) { + foreach ($debugInfo as $codeLine => $templateLine) { + if ($codeLine <= $current['line']) { + $line = $templateLine; + break; + } + } + } + + $src = $object->getSourceContext(); + //$code = preg_split('/\r\n|\r|\n/', $src->getCode()); + //$current['twig']['twig'] = trim($code[$line - 1]); + $current['twig']['file'] = $src->getPath(); + $current['twig']['line'] = $line; + + $prevFile = $previous['file'] ?? null; + if ($prevFile && $file === $prevFile) { + $prevLine = $previous['line']; + + $line = 1; + foreach ($debugInfo as $codeLine => $templateLine) { + if ($codeLine <= $prevLine) { + $line = $templateLine; + break; + } + } + + //$previous['twig']['twig'] = trim($code[$line - 1]); + $previous['twig']['file'] = $src->getPath(); + $previous['twig']['line'] = $line; + } + + $cut = $i; + } elseif ($object instanceof ProcessorInterface) { + $cut = $cut ?: $i; + break; + } + + $previous = &$backtrace[$i]; + } + unset($current); + + if ($cut) { + $backtrace = array_slice($backtrace, 0, $cut + 1); + } + $backtrace = array_values(array_filter($backtrace)); + + // Skip vendor libraries and the method where error was triggered. + foreach ($backtrace as $i => $current) { + if (!isset($current['file'])) { + continue; + } + if (strpos($current['file'], '/vendor/') !== false) { + $cut = $i + 1; + continue; + } + if (isset($current['function']) && ($current['function'] === 'user_error' || $current['function'] === 'trigger_error')) { + $cut = $i + 1; + continue; + } + + break; + } + + if ($cut) { + $backtrace = array_slice($backtrace, $cut); + } + $backtrace = array_values(array_filter($backtrace)); + + $current = reset($backtrace); + + // If the issue happened inside twig file, change the file and line to match that file. + $file = $current['twig']['file'] ?? ''; + if ($file) { + $errfile = $file; + $errline = $current['twig']['line'] ?? 0; + } + + $deprecation = [ + 'scope' => $scope, + 'message' => $errstr, + 'file' => $errfile, + 'line' => $errline, + 'trace' => $backtrace, + 'count' => 1 + ]; + + $this->deprecations[] = $deprecation; + + // Do not pass forward. + return true; + } + + /** + * @return array + */ + protected function getDeprecations(): array + { + if (!$this->deprecations) { + return []; + } + + $list = []; + /** @var array $deprecated */ + foreach ($this->deprecations as $deprecated) { + $list[] = $this->getDepracatedMessage($deprecated)[0]; + } + + return $list; + } + + /** + * @return void + * @throws DebugBarException + */ + protected function addDeprecations() + { + if (!$this->deprecations) { + return; + } + + $collector = new MessagesCollector('deprecated'); + $this->addCollector($collector); + $collector->addMessage('Your site is using following deprecated features:'); + + /** @var array $deprecated */ + foreach ($this->deprecations as $deprecated) { + list($message, $scope) = $this->getDepracatedMessage($deprecated); + + $collector->addMessage($message, $scope); + } + } + + /** + * @param array $deprecated + * @return array + */ + protected function getDepracatedMessage($deprecated) + { + $scope = $deprecated['scope']; + + $trace = []; + if (isset($deprecated['trace'])) { + foreach ($deprecated['trace'] as $current) { + $class = $current['class'] ?? ''; + $type = $current['type'] ?? ''; + $function = $this->getFunction($current); + if (isset($current['file'])) { + $current['file'] = str_replace(GRAV_ROOT . '/', '', $current['file']); + } + + unset($current['class'], $current['type'], $current['function'], $current['args']); + + if (isset($current['twig'])) { + $trace[] = $current['twig']; + } else { + $trace[] = ['call' => $class . $type . $function] + $current; + } + } + } + + $array = [ + 'message' => $deprecated['message'], + 'file' => $deprecated['file'], + 'line' => $deprecated['line'], + 'trace' => $trace + ]; + + return [ + array_filter($array), + $scope + ]; + } + + /** + * @param array $trace + * @return string + */ + protected function getFunction($trace) + { + if (!isset($trace['function'])) { + return ''; + } + + return $trace['function'] . '(' . implode(', ', $trace['args'] ?? []) . ')'; + } + + /** + * @param callable $callable + * @return string + */ + protected function resolveCallable(callable $callable) + { + if (is_array($callable)) { + return get_class($callable[0]) . '->' . $callable[1] . '()'; + } + + return 'unknown'; + } +} diff --git a/system/src/Grav/Common/Errors/BareHandler.php b/system/src/Grav/Common/Errors/BareHandler.php new file mode 100644 index 0000000..36a3122 --- /dev/null +++ b/system/src/Grav/Common/Errors/BareHandler.php @@ -0,0 +1,33 @@ +getInspector(); + $code = $inspector->getException()->getCode(); + if (($code >= 400) && ($code < 600)) { + $this->getRun()->sendHttpCode($code); + } + + return Handler::QUIT; + } +} diff --git a/system/src/Grav/Common/Errors/Errors.php b/system/src/Grav/Common/Errors/Errors.php new file mode 100644 index 0000000..367d8c1 --- /dev/null +++ b/system/src/Grav/Common/Errors/Errors.php @@ -0,0 +1,85 @@ +get('system.errors'); + $jsonRequest = $_SERVER && isset($_SERVER['HTTP_ACCEPT']) && $_SERVER['HTTP_ACCEPT'] === 'application/json'; + + // Setup Whoops-based error handler + $system = new SystemFacade; + $whoops = new Run($system); + + $verbosity = 1; + + if (isset($config['display'])) { + if (is_int($config['display'])) { + $verbosity = $config['display']; + } else { + $verbosity = $config['display'] ? 1 : 0; + } + } + + switch ($verbosity) { + case 1: + $error_page = new PrettyPageHandler(); + $error_page->setPageTitle('Crikey! There was an error...'); + $error_page->addResourcePath(GRAV_ROOT . '/system/assets'); + $error_page->addCustomCss('whoops.css'); + $whoops->prependHandler($error_page); + break; + case -1: + $whoops->prependHandler(new BareHandler); + break; + default: + $whoops->prependHandler(new SimplePageHandler); + break; + } + + if ($jsonRequest || Misc::isAjaxRequest()) { + $whoops->prependHandler(new JsonResponseHandler()); + } + + if (isset($config['log']) && $config['log']) { + $logger = $grav['log']; + $whoops->pushHandler(function ($exception, $inspector, $run) use ($logger) { + try { + $logger->addCritical($exception->getMessage() . ' - Trace: ' . $exception->getTraceAsString()); + } catch (Exception $e) { + echo $e; + } + }); + } + + $whoops->register(); + + // Re-register deprecation handler. + $grav['debugger']->setErrorHandler(); + } +} diff --git a/system/src/Grav/Common/Errors/Resources/error.css b/system/src/Grav/Common/Errors/Resources/error.css new file mode 100644 index 0000000..11ce3fd --- /dev/null +++ b/system/src/Grav/Common/Errors/Resources/error.css @@ -0,0 +1,52 @@ +html, body { + height: 100% +} +body { + margin:0 3rem; + padding:0; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 1.5rem; + line-height: 1.4; + display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */ + display: -moz-box; /* OLD - Firefox 19- (buggy but mostly works) */ + display: -ms-flexbox; /* TWEENER - IE 10 */ + display: -webkit-flex; /* NEW - Chrome */ + display: flex; + -webkit-align-items: center; + align-items: center; + -webkit-justify-content: center; + justify-content: center; +} +.container { + margin: 0rem; + max-width: 600px; + padding-bottom:5rem; +} + +header { + color: #000; + font-size: 4rem; + letter-spacing: 2px; + line-height: 1.1; + margin-bottom: 2rem; +} +p { + font-family: Optima, Segoe, "Segoe UI", Candara, Calibri, Arial, sans-serif; + color: #666; +} + +h5 { + font-weight: normal; + color: #999; + font-size: 1rem; +} + +h6 { + font-weight: normal; + color: #999; +} + +code { + font-weight: bold; + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; +} diff --git a/system/src/Grav/Common/Errors/Resources/layout.html.php b/system/src/Grav/Common/Errors/Resources/layout.html.php new file mode 100644 index 0000000..6699959 --- /dev/null +++ b/system/src/Grav/Common/Errors/Resources/layout.html.php @@ -0,0 +1,30 @@ + + + + + + Whoops there was an error! + + + +